- 投稿日:2020-05-25T23:40:42+09:00
Nuxt.js + Firebase + VuexFireでデータ表示をソートする
Nuxt.js+Firebaseの勉強を始めたばかりです。
FirestoreのデータをWebページに表示できるようになりました。前回: FirebaseのセキュリティルールをWHEREのように使えると思っていた
やりたいこと
Firestoreのデータを取得してWebページに表示するためにVuexFireを利用しています。今回は、画面の表示内容をソートしたいです。
環境
- Firebase 8.3.0
- Vue CLI 4.0.5
- Nuxt.js 2.11.1
- VuexFire
順序の設定はorderByを使う
Firestoreの構成は次の通りです。
コレクション ドキュメント サブコレクション フィールド notes (uid) pages title サブコレクションをtitleの降順にするため、
.orderBy('title', 'desc')
としています。
- ~/pages
- index.vue
- ~/store
- index.js
pages/index.vue(省略) <v-flex v-for="page in pages" xs12 mb-2> <v-card> <v-card-text> {{page.title}} </v-card-text> </v-card> </v-flex> (省略) computed: { ...mapGetters({ pages: 'getPages' }), }, mounted () { let userID = this.$store.getters['auth/getUid']; this.$store.dispatch( 'setPagesRef', db.collection('notes').doc(userID) .collection('pages').orderBy('title', 'desc')); }, (省略)store/index.jsimport { vuexfireMutations, firestoreAction } from 'vuexfire'; import createPersistedState from "vuex-persistedstate"; export const state = () => ({ pages: [], }); export const mutations = { ...vuexfireMutations }; export const getters = { getPages: (state) => { return state.pages; }, }; export const actions = { nuxtClientInit ({ commit, state, dispatch }, { req }) { createPersistedState()(this); }, setPagesRef: firestoreAction(({ bindFirestoreRef }, ref) => { bindFirestoreRef('pages', ref); }), };
- 投稿日:2020-05-25T23:01:03+09:00
【Nuxt.js】プロジェクト作成後にやること
静的サイトを作成する際、
create-nuxt-app
(プロジェクト作成)後に行う環境設定を備忘録としてまとめています。
都度、更新・変更するかもです。目次
- ESLintを追加する
- SASS(SCSS)の使用
- 共通JSの読み込み
- html/head/bodyタグの編集(サイト共通)
- ローカルサーバーのホスト・ポート番号を変更
- build設定
- 画像の保存先について
ESLintを追加する
全てのJavaScriptとVueファイルをlintします。除外したいファイルは
.gitignore
に定義します。
Nuxt.jsではESLintの設定は.eslintrc.json
ではなく、.eslintrc.js
で設定します。各種パッケージをインストール。
npm install -D babel-eslint eslint eslint-loader eslint-plugin-vue
.eslintrc.js
を作成しオプション等を設定・記述する。eslintrc.jsmodule.exports = { root: true, env: { browser: true, es6: true, node: true }, parserOptions: { parser: 'babel-eslint' }, extends: [ 'eslint:recommended', 'plugin:vue/recommended' ], globals: { "$": false, "jQuery": false }, // *.vueファイルをlintにかけるために必要 plugins: [ 'vue' ], rules: { 'indent': ['error', 2], 'linebreak-style': ['error', 'unix'], 'quotes': ['error', 'single'], 'semi': ['error', 'always'], 'no-console': ['warn'], 'no-unused-vars': ['warn'], 'vue/max-attributes-per-line': 'off', 'vue/singleline-html-element-content-newline': 'off' } };
package.json
にlint
タスクを追加します。package.json"scripts": { "lint": "eslint --ext .js,.vue --ignore-path .gitignore ." }開発中、保存時に自動でESLintを実行するには
nuxt.config.js
に以下を追記してください。nuxt.config.jsbuild: { extend(config, ctx) { // 保存時にESLintを実行 if (ctx.isDev && ctx.isClient) { config.module.rules.push({ enforce: "pre", test: /\.(js|vue)$/, loader: "eslint-loader", exclude: /(node_modules)/ }) } } }参考URL:開発ツール - NuxtJS
Sass(SCSS)の使用
必要なパッケージをインストール。
% npm install -D node-sass sass-loaderstyleタグにlang属性を追加すれば認識してくれます。
hoge.vue<style lang="scss"> .hoge { &__child { } } </style>
.browserslistrc
をルートディレクトリに追加browserslistを参考にコンパイルの対象ブラウザを記述してください。
last 2 version Firefox ESR Chrome 28 not dead共通SCSSを読み込み
サイト共通で読み込みたいCSSがある場合は
nuxt.config.js
にパスを記述します。nuxt.config.jsexport default { css: [ { src: '@/assets/scss/style.scss', lang: 'scss' }, ] }SCSSをグローバル化
SCSSの変数やmixinを各コンポーネントで使用したい場合は「@nuxtjs/style-resources」をインストール。
@nuxtjs/style-resources
% npm install -D @nuxtjs/style-resources
nuxt.config.js
にモジュールと該当ファイルのパスを記述します。nuxt.config.jsexport default { modules: [ '@nuxtjs/style-resources', ], styleResources: { scss: [ '@/assets/scss/_var.scss', '@/assets/scss/_mixin.scss', ] } }共通JSの読み込み
- 外部リソースの読み込み
static
から読み込みplugins
から読み込みmiddleware
から読み込み- 外部リソースの読み込みについて補足
外部リソースの読み込み
外部プラグイン・CDN読み込みたい場合はこれ
async
:非同期defer
:遅延body
:</body>
の直前に追加nuxt.config.jsexport default { head: { script: [ { src: 'https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.min.js', async: true, defer: true, body: true }, { src: 'https://polyfill.io/v3/polyfill.min.js?version=3.52.1' }, ] } }参考URL:外部リソースを使うには? - NuxtJS
static
から読み込みwebpackで処理されることを意図しないファイルを読み込む場合はこれ。
外部リソースと同じ方法で読み込みます
static
フォルダに該当ファイルを追加してnuxt.config.js
にパスを記述します。nuxt.config.jsexport default { head: { script: [ { src: 'js/common.js', body: true } ] } }
plugins
から読み込みVueアプリケーションがが初期化される前にインポートしたい場合はこれ。
webpackで処理したい自作のJSを読み込ませる場合、この方法を利用しています。
plugin
フォルダに該当ファイルを追加してnuxt.config.js
にパスを記述します。
mode
:client
クライアントサイドで実行。server
サーバーサイドで実行nuxt.config.jsexport default { plugins: [ { src: '@/plugins/common.js', mode: 'client' } ] }参考URL:API: plugins プロパティ - NuxtJS
middleware
から読み込みVueアプリケーションでルーティングが行われるタイミングで何かしたい場合はこれ。
今のところ具体的な用途がわからない。外部リソースの読み込みについて補足
個人的な手法ですが、リソースのリクエストを減らす目的で、開発時に外部プラグインと自作JSを統合しています。
plugins
フォルダにimport
フォルダを追加し、フォルダ内に外部プラグインのソースコードなどを入れておきます。
あとは自作JSの最初に該当ファイルをimportしておけば、トランスパイルで統合されたファイルが書き出されます。hoge.jsimport './import/jquery.js'; import './import/slick.min..js';外部プラグインにはESLintを動作させたくないので、動作範囲から
import
フォルダを除外します。
.eslintignore
を作成し、除外記述を追加します。plugins/import/*html/head/bodyタグの編集(サイト共通)
個人的に追加した要素だけ記述しています。
titleTemplate
:タイトルタグのテンプレートhtmlAttrs
:htmlタグの属性指定bodyAttrs
:bodyタグの属性指定nuxt.config.jsexport default { head: { titleTemplate: '%s | SAMPLE SITE', htmlAttrs: { lang: 'ja', class: ['html-class'] }, bodyAttrs: { class: ['body-class'] } } }ローカルサーバーのホスト・ポート番号を変更
ローカルIPアドレスでアクセスできるようにして、仮想環境や別デバイスで確認できるように設定します。
nuxt.config.jsexport default { server: { port: 3000, host: '0.0.0.0' } }参考URL:https://ja.nuxtjs.org/faq/host-port/
build設定
postcss
SCSSコンパイルの設定です。
-autoprefixer
:autoprefixerでgridをIEに対応させています。nuxt.config.jsexport default { build: { postcss: { preset: { autoprefixer: { grid: true } } } } }参考URL:API: build プロパティ - NuxtJS
babel
babelの設定です。
targets
:ビルドのターゲットを指定します。
server
ビルドとclient
ビルドでターゲットの初期値が異なります。server
ビルドではnode: 'current'
、client
ビルドではie: '9'
になります。envTarget
では各ビルドのターゲットを定義しています。client
側に空のオブジェクトを定義しておけば.browserslistrc
の設定を参照してくれます。useBuiltIns
:polyfillの処理方法を設定します
usage
またはentry
を指定するとcore-js
(polyfillライブラリ)でpolyfillが補完されます。usage
は必要なpolyfillだけを自動で判別してインポートしてくれます。entry
は個別に手動でpolyfillをインポートします。corejs
:core-jsの読み込みバージョンを指定します。nuxt.config.jsexport default { build: { babel: { presets({ envName }) { const envTarget = { server: { node: "current" }, client: {}, }; return [ [ require.resolve('@nuxt/babel-preset-app'), { targets: envTarget[envName], useBuiltIns: "usage", corejs: { version: 2 } } ] ] } } } }
参考URL - 1:Babel7.x時代のpolyfillの設定方法とuseBuiltInsの仕組み
参考URL - 2:Babel7.4で非推奨になったbabel/polyfillの代替手段と設定方法画像の保存先について
環境構築から脱線しますが、画像リソースの保存先として
assets
フォルダとstatic
フォルダの使い分けを補足しておきます。
基本的に、webpackで処理したいファイルはassets
、それ以外はstatic
で使い分けています。
assets
フォルダこちらに格納したファイルはデータURL(Base64)に変換、インライン化されます。
webpackで処理したいファイルはこちらに格納します。
assets
フォルダの画像参照方法最初にエイリアスを記述するのがポイントです。
hoge.vue<img src="~/assets/images/image.png"> <img src="@/assets/images/image.png">hoge.scssbackground: url("~assets/images/image.png"); background: url("~@/assets/images/image.png");
static
フォルダwebpackで処理したくないファイルはこちらに格納します。
static
フォルダの画像参照方法hoge.vue<img src="/images/image.png">hoge.scssbackground: url("/images/image.png");参考URL - 1:アセット - NuxtJS
参考URL - 2:【Nuxt.js】imgファイルの指定方法について
- 投稿日:2020-05-25T23:01:03+09:00
【Nuxt.js】サイト構築前にやること
静的サイトを作成する際、
create-nuxt-app
(プロジェクト作成)後に行う環境設定を備忘録としてまとめています。
都度、更新・変更するかもです。目次
- ESLintを追加する
- SASS(SCSS)の使用
- 共通JSの読み込み
- html/head/bodyタグの編集(サイト共通)
- ローカルサーバーのホスト・ポート番号を変更
- build設定
- 画像の保存先について
ESLintを追加する
全てのJavaScriptとVueファイルをlintします。除外したいファイルは
.gitignore
に定義します。
Nuxt.jsではESLintの設定は.eslintrc.json
ではなく、.eslintrc.js
で設定します。各種パッケージをインストール。
npm install -D babel-eslint eslint eslint-loader eslint-plugin-vue
.eslintrc.js
を作成しオプション等を設定・記述する。eslintrc.jsmodule.exports = { root: true, env: { browser: true, es6: true, node: true }, parserOptions: { parser: 'babel-eslint' }, extends: [ 'eslint:recommended', 'plugin:vue/recommended' ], globals: { "$": false, "jQuery": false }, // *.vueファイルをlintにかけるために必要 plugins: [ 'vue' ], rules: { 'indent': ['error', 2], 'linebreak-style': ['error', 'unix'], 'quotes': ['error', 'single'], 'semi': ['error', 'always'], 'no-console': ['warn'], 'no-unused-vars': ['warn'], 'vue/max-attributes-per-line': 'off', 'vue/singleline-html-element-content-newline': 'off' } };
package.json
にlint
タスクを追加します。package.json"scripts": { "lint": "eslint --ext .js,.vue --ignore-path .gitignore ." }開発中、保存時に自動でESLintを実行するには
nuxt.config.js
に以下を追記してください。nuxt.config.jsbuild: { extend(config, ctx) { // 保存時にESLintを実行 if (ctx.isDev && ctx.isClient) { config.module.rules.push({ enforce: "pre", test: /\.(js|vue)$/, loader: "eslint-loader", exclude: /(node_modules)/ }) } } }参考URL:開発ツール - NuxtJS
Sass(SCSS)の使用
必要なパッケージをインストール。
% npm install -D node-sass sass-loaderstyleタグにlang属性を追加すれば認識してくれます。
hoge.vue<style lang="scss"> .hoge { &__child { } } </style>
.browserslistrc
をルートディレクトリに追加browserslistを参考にコンパイルの対象ブラウザを記述してください。
last 2 version Firefox ESR Chrome 28 not dead共通SCSSを読み込み
サイト共通で読み込みたいCSSがある場合は
nuxt.config.js
にパスを記述します。nuxt.config.jsexport default { css: [ { src: '@/assets/scss/style.scss', lang: 'scss' }, ] }SCSSをグローバル化
SCSSの変数やmixinを各コンポーネントで使用したい場合は「@nuxtjs/style-resources」をインストール。
@nuxtjs/style-resources
% npm install -D @nuxtjs/style-resources
nuxt.config.js
にモジュールと該当ファイルのパスを記述します。nuxt.config.jsexport default { modules: [ '@nuxtjs/style-resources', ], styleResources: { scss: [ '@/assets/scss/_var.scss', '@/assets/scss/_mixin.scss', ] } }共通JSの読み込み
- 外部リソースの読み込み
static
から読み込みplugins
から読み込みmiddleware
から読み込み- 外部リソースの読み込みについて補足
外部リソースの読み込み
外部プラグイン・CDN読み込みたい場合はこれ
async
:非同期defer
:遅延body
:</body>
の直前に追加nuxt.config.jsexport default { head: { script: [ { src: 'https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.min.js', async: true, defer: true, body: true }, { src: 'https://polyfill.io/v3/polyfill.min.js?version=3.52.1' }, ] } }参考URL:外部リソースを使うには? - NuxtJS
static
から読み込みwebpackで処理されることを意図しないファイルを読み込む場合はこれ。
外部リソースと同じ方法で読み込みます
static
フォルダに該当ファイルを追加してnuxt.config.js
にパスを記述します。nuxt.config.jsexport default { head: { script: [ { src: 'js/common.js', body: true } ] } }
plugins
から読み込みVueアプリケーションがが初期化される前にインポートしたい場合はこれ。
webpackで処理したい自作のJSを読み込ませる場合、この方法を利用しています。
plugin
フォルダに該当ファイルを追加してnuxt.config.js
にパスを記述します。
mode
:client
クライアントサイドで実行。server
サーバーサイドで実行nuxt.config.jsexport default { plugins: [ { src: '@/plugins/common.js', mode: 'client' } ] }参考URL:API: plugins プロパティ - NuxtJS
middleware
から読み込みVueアプリケーションでルーティングが行われるタイミングで何かしたい場合はこれ。
今のところ具体的な用途がわからない。外部リソースの読み込みについて補足
個人的な手法ですが、リソースのリクエストを減らす目的で、開発時に外部プラグインと自作JSを統合しています。
plugins
フォルダにimport
フォルダを追加し、フォルダ内に外部プラグインのソースコードなどを入れておきます。
あとは自作JSの最初に該当ファイルをimportしておけば、トランスパイルで統合されたファイルが書き出されます。hoge.jsimport './import/jquery.js'; import './import/slick.min..js';外部プラグインにはESLintを動作させたくないので、動作範囲から
import
フォルダを除外します。
.eslintignore
を作成し、除外記述を追加します。plugins/import/*html/head/bodyタグの編集(サイト共通)
個人的に追加した要素だけ記述しています。
titleTemplate
:タイトルタグのテンプレートhtmlAttrs
:htmlタグの属性指定bodyAttrs
:bodyタグの属性指定nuxt.config.jsexport default { head: { titleTemplate: '%s | SAMPLE SITE', htmlAttrs: { lang: 'ja', class: ['html-class'] }, bodyAttrs: { class: ['body-class'] } } }ローカルサーバーのホスト・ポート番号を変更
ローカルIPアドレスでアクセスできるようにして、仮想環境や別デバイスで確認できるように設定します。
nuxt.config.jsexport default { server: { port: 3000, host: '0.0.0.0' } }参考URL:https://ja.nuxtjs.org/faq/host-port/
build設定
postcss
SCSSコンパイルの設定です。
-autoprefixer
:autoprefixerでgridをIEに対応させています。nuxt.config.jsexport default { build: { postcss: { preset: { autoprefixer: { grid: true } } } } }参考URL:API: build プロパティ - NuxtJS
babel
babelの設定です。
targets
:ビルドのターゲットを指定します。
server
ビルドとclient
ビルドでターゲットの初期値が異なります。server
ビルドではnode: 'current'
、client
ビルドではie: '9'
になります。envTarget
では各ビルドのターゲットを定義しています。client
側に空のオブジェクトを定義しておけば.browserslistrc
の設定を参照してくれます。useBuiltIns
:polyfillの処理方法を設定します
usage
またはentry
を指定するとcore-js
(polyfillライブラリ)でpolyfillが補完されます。usage
は必要なpolyfillだけを自動で判別してインポートしてくれます。entry
は個別に手動でpolyfillをインポートします。corejs
:core-jsの読み込みバージョンを指定します。nuxt.config.jsexport default { build: { babel: { presets({ envName }) { const envTarget = { server: { node: "current" }, client: {}, }; return [ [ require.resolve('@nuxt/babel-preset-app'), { targets: envTarget[envName], useBuiltIns: "usage", corejs: { version: 2 } } ] ] } } } }
参考URL - 1:Babel7.x時代のpolyfillの設定方法とuseBuiltInsの仕組み
参考URL - 2:Babel7.4で非推奨になったbabel/polyfillの代替手段と設定方法画像の保存先について
環境構築から脱線しますが、画像リソースの保存先として
assets
フォルダとstatic
フォルダの使い分けを補足しておきます。
基本的に、webpackで処理したいファイルはassets
、それ以外はstatic
で使い分けています。
assets
フォルダこちらに格納したファイルはデータURL(Base64)に変換、インライン化されます。
webpackで処理したいファイルはこちらに格納します。
assets
フォルダの画像参照方法最初にエイリアスを記述するのがポイントです。
hoge.vue<img src="~/assets/images/image.png"> <img src="@/assets/images/image.png">hoge.scssbackground: url("~assets/images/image.png"); background: url("~@/assets/images/image.png");
static
フォルダwebpackで処理したくないファイルはこちらに格納します。
static
フォルダの画像参照方法hoge.vue<img src="/images/image.png">hoge.scssbackground: url("/images/image.png");参考URL - 1:アセット - NuxtJS
参考URL - 2:【Nuxt.js】imgファイルの指定方法について
- 投稿日:2020-05-25T22:36:56+09:00
Githubでプルリクをするとき"There isn't anything to compare"といわれたときの対処法
問題
プロジェクトも終わりが見えてきて、制作中のbranchをmasterとmergeしたいとき。
まず制作中のbranchからpull requestをします。(Github上で行えます)
ところがどっこいこのようなメッセージが出てきました。There isn’t anything to compare. master and edit_branch are entirely different commit histories.masterとedit_branch(制作branch)は全く異なるコミットを持つものです。比較できません。
というような訳かとおもいます。もっというと、この二つはそもそも何の関係性も持たないので、remote(Github上)では、比較するなんてことはできません。ということのようです(間違っていたらご指摘ください…)。
解決策
remote上で、edit_branchのmasterは、これよ〜と知らせるためにターミナルで以下を打ち込みます。
git branch --set-upstream-to=<remote>/<branch> masterこれでedit_branchとmasterの関係性が構築できました。
あとは以下を叩きます。git checkout edit_branch git pull git checkout master git pull git merge edit_branch git push origin masterこの順でmergeまで完了しました。
備忘録でした。
- 投稿日:2020-05-25T20:53:59+09:00
SpeechRecognitionで繰り返しマイクを拾い続ける
話している言葉をずっとテキストに起こし続けたかった
そのためにはマイクをずっと動かして声を拾い続けないといけません。SpeechRecognitionは時間が一定経つと自動的に終わってしまいます。その回避策です。
そもそも何故そうしたかったのかというとリアルタイムに声をテキストとして表示する「Zimack」というサイトを作った というので詳細を書いているのですが動画配信用に字幕を出すものが欲しかったためです。それで手ごろだったのがVueでwebページを作ってOBSでキャプチャすることでした。
実装内容
今回の実装はApp.vueにメソッドとして書いています。それを切り抜いて以下のような流れになります。
data: () => ({ speechRecognition: window.SpeechRecognition || window.webkitSpeechRecognition, isFirstStarted: false, -----省略------ methods: { settingRecognition () { var recognition = new this.speechRecognition() recognition.lang = 'ja-JP' recognition.interimResults = true recognition.continuous = true // start時のコールバック、フラグを立てる recognition.addEventListener('start', () => { this.isFirstStarted = true }) // テキスト起こしの途中経過が入ってくる recognition.addEventListener('result', event => { const stackText = Array.from(event.results).map(x => x[0]).map(x => x.transcript) this.currentText = stackText.join('。') }) // end時のコールバック、複数回呼ばれる可能性があるのでフラグが立っていれば再度テキスト起こしを始める recognition.addEventListener('end', () => { if (this.isFirstStarted) { recognition.start() this.isFirstStarted = false } }) recognition.start() } }, -----省略------ mounted () { // SpeechRecognitionが扱えるのであればundefinedにならない if (!this.speechRecognition) { alert('ChromeなどのSpeechRecognitionに対応したブラウザをお使いください。') return } this.settingRecognition() -----省略------endが複数回呼ばれたりして適切なタイミングで
start()
を行わないとエラーが出たりブラウザが重くなったりしていました。まとめ
サクッと1日くらいで作ったものなので変数名や処理の流れの雑さは否めないのですが自分は躓いたのでメモ書きとして残します。
Youtubeライブとかの配信でも字幕を出せるようになるので意外と面白いです。すこし間違ったテキストになるのもまた好きなのでサクッと実装できるこのようなapiがあることに感謝ァ…
- 投稿日:2020-05-25T20:50:12+09:00
【Vue + Firebase】投票し放題の人気投票を作った記録
以前もあった、1週間でwebサービスを作るweb1weekというイベントの第二弾です。今回のテーマは「Like」でした。
今回も参加させていただきました!滑り込みセーフ!作ったもの
こんな感じの人気投票です。操作については特に説明はいらないと思います。
また争いが生まれてしまう……。https://like-ranking.web.app/
※ 一日の制限に達すると閲覧すらできなくなります。
コンセプト
あなたの熱意が票になる。
あなたは好きなだけ投票することができます。
あなたの頑張りがそのまま票に反映されます。
あなたの成果はランキングで可視化されます。
あなたと共に戦う仲間やライバルが見えます。人気投票というものは基本的には同一投票者による投票は1票までと決まっています。
ただ、「人気」を「愛され度」というなら、投票者の熱意が反映されないのはおかしいのではないだろうか。
某アイドルグループの人気投票はお金を積むことで熱意を示すわけですが、ここではクリックという時間と手間に置き換えることにしましょう。
そして、せっかく努力をするなら見返りというか、達成感のようなものがあるとなおよい。アイドルの例でいう「認知」のようなものを。技術的なこと
使用したもの
- Vue-cli
- Firebase(Firestore / 認証 / Hosting)
- DesignEvo(ファビコン)
Firebaseについて
機能上データベースが必要なのですが、Firebaseが手軽にバックエンドを実装できるということなので、これを機に手を出してみました。
匿名認証という、まさに私が欲しい機能があったのが最高でした。
データベースの形式がNoSQLというもので(聞いたことがある!)、今まで使ってきたものとは違ったので使い勝手に慣れませんでした。
でも、話で聞いていたとおり、デプロイがビビるほど簡単でした。yarn build firebase deploy
だけでデプロイできる。強い。
リアルタイムでデータが反映されると書いていたので、まさか……と思ったら本当に反映されておもしろかったです。
作成記録
5/19 構想をメモ
そういえば企画のお題が発表されているはず!と思い出して確認する。
自分自身、「ボタンを押すと数字が増える」というのが好きだったので、なんとなくLikeの成分と絡めて、ボタン押し放題→投票→コンセプトという感じで
- 投票し放題
- ユーザーの投票数に応じたランキング
というコアの機能を決めました。
関連付けを使いたくないなあと思ってこういうデータベース構造を考えたのですが、これは非常によくなかった。
ちなみに、rankings(rightName, rightCount, leftName, leftCount), users(uid, name, leftCount, rightCount)という感じの構成でした。5/20 Firebaseの設定
3時間くらい。
Firebaseの登録をしてデプロイをしようとしたが、HelloWorldのデプロイがうまくできなかったので一旦諦める。5/23 無制限投票機能
13時間くらい。
思い立って新しく作り直してやってみたところ、何のことはなくあっさりとデプロイができたので、作成開始。
ググりながらQiitaと個人ブログと公式とを行ったり来たりしながらデータを渡す。データが渡せればあとは表示するだけ。
久しぶりにCSSを触ったので楽しかったです。フォントやボタンはモロゾフプリン対決のサイトを参考にしました。このボタンがまさに押したくなるボタンのデザインで、やっぱり企業サイトはすごいと思いました。5/24 ユーザー認証、ユーザーデータの表示
11時間くらい。
ユーザー認証(アクセスした時点でログインしてuserデータを登録をする)自体は前日の最後にできていたので、名前変更機能やランキング部分を作りました。
そして、なんとか公開に至りました。
デプロイして一息ついた後に風呂から帰ってきたら制限で落ちていたので、まじかと思いました。公開して終わりにしようと思っていたのですが、機能としてわりと致命的なので、まあできるだけのことはしようと思いました。5/25 リクエスト部分の修正
4時間くらい。
データ制限が解除された5時頃から作業を開始しました。
元々、投票したときにrankingとuser両方のデータ更新をしたり、並び替えの仕方がよくわからなかったのでランキングの各コンポーネントでそれぞれusersを取得したりしていました。そしていろいろと試していると、途中でデータベースの中身をいじらないといけないということに気づきました。具体的に言うと、全てのユーザーに
leftCount: 0
のようなカラムを手作業で入れなければならないということに気づきました。
そして、目の前でカウントが増えていくのを見ていると胃が痛くなりそうだったので、一旦非公開にしました。今までJavaScriptでロジックを書くということをしていなかったので、並べ替えやデータ集計などに苦戦しました。SQLで引っ張ってこれないの面倒だ!
あと、データバインディングで死にかけました。sortって破壊的メソッドなんですね……。結果的に、rankingはなくして、きのこたけのこ限定にしました。usersのleftCount, rightCountをそれぞれ集計して投票数として表示しています(本来は元々こうするべきなんだろう)。
おわりに
最初のデプロイに挫折して今回は諦め気味だったので、提出できてよかったです。
こういうサービスというのはいろんな人に使われているのを見るのが醍醐味でもあると思うのですが、企画を通していろんな人の目に触れられることができたのでとても嬉しかったです!!
このような機会を設けてくださった主催者のだら様に感謝申し上げます。そして、私がtimesに進捗を挙げるたびに褒めてくれたり、テストプレイ?に協力してくれたRUNTEQの皆さんも本当にありがとうございました。
ちなみに私はきのこもたけのこもおいしくいただく反逆者です。
課題
- 投稿日:2020-05-25T18:08:37+09:00
React vs Vue.js vs Angular.js 【データバインディング編】
この内容について
この内容は、私が運営しているサイトの一部抜粋です。よければそちらもご活用ください。
Reactチートシート | コレワカ
Vue.jsチートシート | コレワカ
AngularJSチートシート | コレワカそれぞれの特徴
React Vue.js AngularJS 特徴 状態管理に特化したUI構築のためのライブラリ トランスコンパイル不要なUI構築のためのライブラリ 大抵の機能が全て揃うフルスタックなフレームワーク 開発規模 小規模〜大規模 小規模〜中規模 中規模〜大規模 組み合わせ Redux・TypeScript・webpack・babelなど Vuex・Laravel・Firebaseなど TypeScript・AWSなど データバインディングとは
データと描画を同期する仕組みのこと
それぞれのコード
React
See the Pen React_onChange by engineerhikaru (@engineerhikaru) on CodePen.
Vue.js
See the Pen Vue.js_v-model by engineerhikaru (@engineerhikaru) on CodePen.
AngularJS
See the Pen AngularJS_ng-model by engineerhikaru (@engineerhikaru) on CodePen.
簡単な解説
React
Reactは、単方向データバインディングなので、
setStateでデータを保管し、changeイベンド(onChange)で、View ⇆ Modelを実現しています。Vue.js
Vue.jsは、双方向データバインディングなので、
Model関数(v-model)を使って、View ⇆ Modelを実現しています。AngularJS
AngularJSは、双方向データバインディングなので、
Model関数(ng-model)を使って、View ⇆ Modelを実現しています。おまけ
jQueryで書いた場合
キーが押された時に処理を実行するkeyup関数とテキスト出力をするためのtext関数を使って、
View ⇆ Modelを実現しています。
See the Pen
jQuery_databinding by engineerhikaru (@engineerhikaru)
on CodePen.
- 投稿日:2020-05-25T17:55:16+09:00
nuxt-custom-elementsを試してみる
はじめに
vue-custom-elementsについて調べていた際に、nuxt-custom-elementsというのを見つけたので試してみる。
vue-custom-elementsのnuxt版っぽいものかな。Git Hub
https://github.com/GrabarzUndPartner/nuxt-custom-elementsnuxt-custom-elements
READMEの説明(の翻訳)によると、
nuxt-custom-elementsは、vue-custom-elementを使用して単一のコンポーネントをカスタム要素/ web-componentとして公開するNuxt.jsモジュールです。
とのこと。
npmでダウンロードされ始めたのが、2020/1頃で、「Last publish」も「2 months ago」なので、比較的新しいモジュールっぽい。
nuxt-custom-elementsのPreview
とりあえず、READMEのPreviewを試してみる。
1. Clone this repository 2. Install dependencies using yarn install or npm install 3. Build and start with express npm run start:build 4. Open http://127.0.0.1:3000 in Browser.まず、クローン。
> git clone https://github.com/GrabarzUndPartner/nuxt-custom-elements.gitそれからインストール
> yarn installビルドの前に、package.jsonのscriptsを見てみる
package.json... "scripts": { "dev": "nuxt example", "generate": "nuxt generate --config-file example/nuxt.config.js", "build": "nuxt build --config-file example/nuxt.config.js", "start:build": "yarn build && yarn express-server --dist example/.nuxt/nuxt-custom-elements/dist", "lint": "yarn lint:js && yarn lint:css", "lint:js": "eslint --ext .js,.vue example lib test", "lint:css": "stylelint \"example/**/*.vue\" \"example/**/*.css\", \"lib/**/*.vue\" \"lib/**/*.css\"", "release": "yarn test && standard-version && git push --follow-tags && npm publish", "express-server": "node ./tools/express-server.js --dist dist/nuxt-custom-elements", "test": "yarn lint && jest" }, ...READMEに書いてある"start:build"を実行すると、下記コマンドが実行されるらしい。
yarn build && yarn express-server --dist example/.nuxt/nuxt-custom-elements/distつまり、
nuxt build --config-file example/nuxt.config.jsを実行して、その後に下のようにサーバーを起動する感じですね。
node ./tools/express-server.js --dist dist/nuxt-custom-elements元コマンドの最後の--distがどこに効いてくるのか気になるが、
とりあえず、ビルドコマンドを実行してみる。> yarn start:buildとくにエラーなくビルドできたようなので、書いてある通りに「http://127.0.0.1:3000」にアクセスしてみる。
ん?違うらしい。
コマンド上は下記の通り、特にエラーらしきものは見当たらず、サーバーが正常に起動できているように見える。(やっぱり--distが2回指定されているのは気になるが。。。)... $ node ./tools/express-server.js --dist dist/nuxt-custom-elements --dist example/.nuxt/nuxt-custom-elements/dist server listening on http://127.0.0.1:3000 ; dist: dist/nuxt-custom-elements実行しているexpress-server.jsを見てみると、distで指定したパス以下を静的ファイルとしてホスティングしている模様。
express-server.js... app.use(express.static(dist)) app.listen(port, host, function () { const { address, port } = this.address() // eslint-disable-next-line no-console console.log(`server listening on http://${address}:${port} ; dist: ${dist}`) }) ...コンソールには、dist: dist/nuxt-custom-elementsと表示されているので、ここを見てみる。
... - nuxt-custom-elements/ - component-app-abstract/ - 0.4de0fb9e8e479190b737.js - ... - index.html - component-app-bundle/ - ... - component-app-hash/ - ... - component-app-history/ - ... - test.json ...うん。確かに「http://127.0.0.1:3000/」でアクセスしてもダメそう。
とりあえず、「http://127.0.0.1:3000/test.json」でアクセスしてみると、なんだかいけそうな気がしてきた。
ということは、「http://localhost:3000/component-app-abstract」か。無事コンポーネント(を呼び出しているindex.html)が表示されました。
コンポーネントの呼び出し方はこんな感じになっているようです。
index.html... <custom-element-app-abstract base-path="./"></custom-element-app-abstract> <script type="text/javascript" src="./component-app-abstract.b773b4f5571aec71cd92.js"></script> ...nuxt-custom-elementsを利用してコンポーネントを作ってみる
まずは、nuxt公式サイトにある「スクラッチから始める」でnuxt環境を構築。
(create-nuxt-appを使うと、逆に訳がわからなくなりそうなので。)> mkdir nuxt-component > cd nuxt-componentpackage.jsonの設定は適当にこんな感じ
package.json{ "name": "nuxt-component", "scripts": { "dev": "nuxt" } }nuxtをインストールする。
> yarn add nuxtpagesディレクトリを作って、
> mkdir pagespages/index.vueを作成
index.vue<template> <h1>Hello world!</h1> </template>とりあえず、シンプルなnuxtアプリを起動してみる。
> yarn devシンプルなnuxtアプリが出来たので、nuxt-custom-elementsのREADMEに従って、セットアップを進めてみる。
nuxt-custom-elementsのインストール。yarn add nuxt-custom-elements続いて、nuxt.config.jsを作成
nuxt.config.jsmodule.exports = { modules: [ [ "nuxt-custom-elements", { polyfill: true, entries: [ { name: "ComponentHelloWorld", tags: [ { name: "ComponentHelloWorld", path: "@/pages/index", options: { props: {}, shadow: false, }, }, ], }, ], }, ], ], };※propsを指定しないとビルド時にエラーになるので、意味ないけど指定しています。
コード(tag.js)を見る限り、nuxt-custom-elementsの不具合っぽいです。package.jsonを編集してビルドや静的ホスティングができるようにする。
package.json... "scripts": { "dev": "nuxt", "build": "nuxt build", "generate": "nuxt generate" }, ...ビルドしてみる。
> yarn build成功すると、.nuxt以下にnuxt-custom-elementsというフォルダが生成され、成果物が格納されている。
※yarn generateを実行した場合は、dist以下に生成される。
一緒に生成されるindex.htmlを見ると以下のような感じで呼び出せる模様。
index.html<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>component-hello-world</title> </head> <body> <component-hello-world ></component-hello-world> <script type="text/javascript" src="./component-hello-world.4254383c620976e0504d.js"></script> </body> </html>コードはここに置いときました。
https://github.com/yusuke-ka/sample-nce-simple-nuxt
気になった点
生成されたjsファイル一式をS3とかで静的ホスティングすれば、外部からも呼び出せるようになるんだろうけど、「4254383c620976e0504d」の部分が余計な気がする。
これ、なんで付いているんだろ。「component-hello-world.js」になっていれば、呼び出す側も下のように固定で呼び出せるのに。
<body> <component-hello-world ></component-hello-world> <script type="text/javascript" src="./component-hello-world.js"></script> </body>
- 投稿日:2020-05-25T09:15:25+09:00
【Vue.js】各種分類まとめ
はじめに
Vue.jsをさて始めようと思ったときに、そもそもいろんな種類がありすぎて混乱しました。どれも似たような名前だし、何から始めればいいのかわからない。
それでもいろいろと手を出していくに従って「これはこのためのものかー」というのがわかってきたので、過去の自分のような初学者の頭が整理されればいいなと思い、Vue関係のあれこれをまとめることにしました。想定読者
- Vueにチャレンジしてみたいけど、何から始めればいいの?
- 使ってみようとしたけど、インストール方法が多くて何を選べばいいのかわからない。
- Vueほにゃららシリーズ多くない?
Vue.js
Vueはユーザーインターフェイスを構築するためのプログレッシブフレームワークです。
Vue.jsはJavaScriptという言語のフレームワークの一種です。
JavaScriptの上位(見方によっては下位?)に位置し、React、Angularと同じ階層です。
ちなみに、Vueの公式サイトでは他のフレームワークとの比較もしています。jQueryは?
jQueryはライブラリなので、フレームワークとは少し違います。ちょっとした動きをつけたい場合や、一部だけ非同期処理をしたいときには使い勝手がいいです。
この図に関しては、なんとなくのイメージで捉えていただければと思います。インストールの仕方
公式サイトを見ると、インストールの方法もさまざまです。npmは少し特殊な立ち位置のようです。
CDN
普通のHTMLにタグを埋め込むだけで使えます。
一番簡単なので、初めてVueを使って何か作りたいという人にはおすすめです。CDN版を使う利点
・Node.jsをインストールしなくて済む
・npmについて知らなくても済む
・jQueryを使ってた時と同じノリで使えて分かりやすい
・Rails等のサーバサイドフレームワーク使ってるときに、構成で悩まなくて済む
・webpackまたはvue-cliではまらない
VueのCDN版を使おう - やわらかVue.jsVue-cli
全部載せお得パック。とりあえずこれに乗っかっておけば間違いないというもの。作成時に下記のプラグインなどを使うかどうか選択できます。
いわゆる.vue
ファイル(単一ファイルコンポーネント)にコードを記述し、コンパイルされたものを読み込むという原理です。コンパイラが必要ですが、作成時に選択できるのでそれほど考える必要はないと思います。npm
npmというパッケージツールを用いてインストールします。
Node.jsを使ってごりごり書くときに使うという印象。単一ファイルコンポーネントを使わず、HTML, CSS, JSを分離させた形で使われる。
Vueを使おう、という文脈で使われているのをあまり見たことがないので、ロードマップからは外れているようです。プラグインなど
上記でVue.jsを使えるようになったことを前提として追加できるプラグインです。
参考:Vue の拡張必要に応じて自由にプラグインを追加できるのが、Vueが「他の一枚板(モノリシック: monolithic)なフレームワークとは異なり、Vue は少しずつ適用していけるように設計されています。」と謳う理由なのかなと思います。
Vue-router
Vue Router は Vue.js 公式ルータです。これは Vue.js のコアと深く深く統合されており、Vue.js でシングルページアプリケーションを構築します。
すべての画面をルートページだけで完結するなら不要ですが、大抵はそうではないのでこれを使います。
ルーティングによってURLを変更できるので、ページ遷移しているような挙動にできます。
コンポーネントの表示を切り替えることでページ遷移のようなものを再現することはできますが、更新ボタンを押すとページがリセットされます。Vuex
Vuex は Vue.js アプリケーションのための 状態管理パターン + ライブラリです。 これは予測可能な方法によってのみ状態の変異を行うというルールを保証し、アプリケーション内の全てのコンポーネントのための集中型のストアとして機能します。
「状態」というのが自分の中でしっくり来なかったのですが、例えば、ログインしている状態、〇〇のページを開いている状態とイメージするとなんとなくわかるような気がします。
私が勉強会などで聞いた限りでは、単純なデータの受け渡しのショートカットとしてVuexを使うのはアンチパターンとされています(公式は推奨していますが)。propsやemitを使って愚直にデータのバケツリレーをしたほうがいいという意見が多いようです。
参考:Vuexで何をするか、しないか
「使いどころ」からはじめるVuexNuxt.js
Vueのプロジェクトで必要になるであろうプラグインが標準で全部入っているフレームワークです。Vue-cliよりもっと全部載せで、規約に従うことでよしなにしてくれます。
ディレクトリ構造を見ると分かる通り、「これはこのディレクトリに入れてね」というのがかなり明確に決められています。
また、ディレクトリ構造から推測して勝手にルーティングを設定してくれます。(Vue-routerもいらないというのはこういうこと)
SSRを選択できるというのも大きな特徴です。
参考:NUXT いるのかどうか (Vue CLI 3 との比較)UIフレームワーク
コンポーネントフレームワークのテンプレート。コンポーネントを指定するだけでいい感じのデザインにしてくれます。
例えばVuetifyでは<v-btn>ボタン</v-btn>
と書くだけで以下のようなボタンができます。
種類としては、以下のようなものがあります。おわりに
初見の目線で説明をしました。Vue.jsに初めて触れる人が少しでも理解がしやすくなると幸いです。
ちなみに、ここに挙げた全ての技術を使ったことがあるわけではないので、把握しきれていないところもあります。間違いなどがございましたらご指摘いただければと思います。
- 投稿日:2020-05-25T09:15:25+09:00
【Vue.js】初学者向け各種分類まとめ
はじめに
Vue.jsをさて始めようと思ったときに、そもそもいろんな種類がありすぎて混乱しました。どれも似たような名前だし、何から始めればいいのかわからない。
それでもいろいろと手を出していくに従って「これはこのためのものかー」というのがわかってきたので、過去の自分のような初学者の頭が整理されればいいなと思い、Vue関係のあれこれをまとめることにしました。想定読者
- Vueにチャレンジしてみたいけど、何から始めればいいの?
- 使ってみようとしたけど、インストール方法が多くて何を選べばいいのかわからない。
- Vueほにゃららシリーズ多くない?
Vue.js
Vueはユーザーインターフェイスを構築するためのプログレッシブフレームワークです。
Vue.jsはJavaScriptという言語のフレームワークの一種です。
JavaScriptの上位(見方によっては下位?)に位置し、React、Angularと同じ階層です。
ちなみに、Vueの公式サイトでは他のフレームワークとの比較もしています。jQueryは?
jQueryはライブラリなので、フレームワークとは少し違います。ちょっとした動きをつけたい場合や、一部だけ非同期処理をしたいときには使い勝手がいいです。
この図に関しては雰囲気だけなんとなくのイメージで捉えていただければと思います。(追記)
Reactは公式でライブラリと書かれていました。
が、実際はフレームワークとしてVue.jsやAngularと同列に扱われがちです。インストールの仕方
公式サイトを見ると、インストールの方法もさまざまです。npmは少し特殊な立ち位置のようです。
初心者はどれから始めればいいのか、というと、私個人の意見としては「1ファイルで済むならCDN、そうでないならVue-cli。npmは自分が行けると思うならどうぞ」です。
参考:CDN版で大きなアプリ作れるの?CDN
普通のHTMLにタグを埋め込むだけで使えます。
一番簡単なので、初めてVueを使って何か作りたいという人にはおすすめです。CDN版を使う利点
・Node.jsをインストールしなくて済む
・npmについて知らなくても済む
・jQueryを使ってた時と同じノリで使えて分かりやすい
・Rails等のサーバサイドフレームワーク使ってるときに、構成で悩まなくて済む
・webpackまたはvue-cliではまらない
VueのCDN版を使おうVue-cli
全部載せお得パック。とりあえずこれに乗っかっておけば間違いないというもの。作成時に下記のプラグインなどを使うかどうか選択できます。
いわゆる.vue
ファイル(単一ファイルコンポーネント)にコードを記述し、コンパイルされたものを読み込むという原理です。コンパイラが必要ですが、作成時に選択できるのでそれほど考える必要はないと思います。npm
npmというパッケージツールを用いてインストールします。
Node.jsを使ってごりごり書くときに使うという印象。設定ファイルなどを自分でカスタマイズする場合はこれでインストールするようです。
Vueを使おう、という文脈で使われているのをあまり見たことがないので、学習ロードマップからは外れているようです。多分、これを理解できる人はこの記事は不要です。プラグインなど
上記でVue.jsを使えるようになったことを前提として追加できるプラグインです。
参考:Vue の拡張必要に応じて自由にプラグインを追加できるのが、Vueが「他の一枚板(モノリシック: monolithic)なフレームワークとは異なり、Vue は少しずつ適用していけるように設計されています。」と謳う理由なのかなと思います。
Vue-router
Vue Router は Vue.js 公式ルータです。これは Vue.js のコアと深く深く統合されており、Vue.js でシングルページアプリケーションを構築します。
すべての画面をルートページだけで完結するなら不要ですが、大抵はそうではないのでこれを使います。
ルーティングによってURLを変更できるので、ページ遷移しているような挙動にできます。
コンポーネントの表示を切り替えることでページ遷移のようなものを再現することはできますが、更新ボタンを押すとページがリセットされます。Vuex
Vuex は Vue.js アプリケーションのための 状態管理パターン + ライブラリです。 これは予測可能な方法によってのみ状態の変異を行うというルールを保証し、アプリケーション内の全てのコンポーネントのための集中型のストアとして機能します。
「状態」というのが自分の中でしっくり来なかったのですが、例えば、ログインしている状態、〇〇のページを開いている状態とイメージするとなんとなくわかるような気がします。
私が勉強会などで聞いた限りでは、単純なデータの受け渡しのショートカットとしてVuexを使うのはアンチパターンとされています(公式は推奨していますが)。propsやemitを使って愚直にデータのバケツリレーをしたほうがいいという意見が多いようです。
参考:Vuexで何をするか、しないか
「使いどころ」からはじめるVuexNuxt.js
Vueのプロジェクトで必要になるであろうプラグインが標準で全部入っているフレームワークです。Vue-cliよりもっと全部載せで、規約に従うことでよしなにしてくれます。
ディレクトリ構造を見ると分かる通り、「これはこのディレクトリに入れてね」というのがかなり明確に決められています。
また、ディレクトリ構造から推測して勝手にルーティングを設定してくれます。(Vue-routerもいらないというのはこういうこと)
SSRを選択できるというのも大きな特徴です。
参考:NUXT いるのかどうか (Vue CLI 3 との比較)UIフレームワーク
コンポーネントフレームワークのテンプレート。コンポーネントを指定するだけでいい感じのデザインにしてくれます。
例えばVuetifyでは<v-btn>ボタン</v-btn>
と書くだけで以下のようなボタンができます。
種類としては、以下のようなものがあります。おわりに
初見の目線で説明をしました。Vue.jsに初めて触れる人が少しでも理解がしやすくなると幸いです。
ちなみに、ここに挙げた全ての技術を使ったことがあるわけではないので、把握しきれていないところもあります。間違いなどがございましたらご指摘いただければと思います。
- 投稿日:2020-05-25T02:27:01+09:00
CSSだけで幻想的な光を放つニキシー管を作れるのか?
はじめに
htmlとCSSでニキシー管を作ってみました。(デモのページ)
動的な数字の切り替えに対応しているので、いろいろな用途で使えると思います。ニキシー管とは
ニキシー管は文字や数字を表示させる発光装置です。ガラス管の中にネオンガスが入っていて、電圧を印加するとワイヤーが幻想的なオレンジ色の光を放ちます。今は数字を表示するデバイスは6セグのLEDにとって代わられてしまっていますが、そのスチームパンクな外観に加えて、デジタルな制御なのに表示される数字がアナログという良さから愛好家が多いデバイスです。STEINS;GATEのダイバージェンスメーターに使われているのはあまりにも有名です。
なぜ作ろうと思ったのか
ニキシー管は今は量産されておらず、手に入れようと思うと基本的にはソ連製のデッドストックを探してきて買うしかありません。供給がほとんどないので、1個あたりの値段は結構高いです。例えばこれとかは6個で1万円します。
勢いあまって購入したはいいのですが、ちゃんと制御しようとすると、140Vの電圧を作る昇圧回路、印加するピンを切り替える回路、複数本使う場合にはダイナミック点灯するための同期回路、そしてなんといっても1本あたり10本のピンが出ているのでそれの配線などなどが必要になりとても大変です。そこまでの覚悟がなかったので、手動で数字を点灯するところまでやって投げることになってしまいました...
ずっと家にいると外の世界が大変なことになってるという意識が薄れてくるので、危機感を維持するためにニキシー管で東京都の1日の感染者数を表示するデバイスを作り始めた
— みがわり (@interimadd) 2020年4月5日
3桁もあれば足りるはず…! pic.twitter.com/ejjhPJtkohリアルでは挫折してしまったのですが、web上なら回路を作る必要もないし制御もjavascriptとかで簡単にできるし、いいことづくめなのではと思って作り始めました。
できたもの
いろいろと試行錯誤を繰り返した結果、こんな感じになりました。
もっといい感じのニキシー管を作り上げてくれる人が現れることを願って、これ以降は制作工程を1から書いていきます。ニキシー管の作り方
フォントを選ぶ
ニキシー管の独特な数字を再現するために、適切なフォントを探す必要があります。
ニキシー管で表示される数字は、ワイヤーで作られているものなので、次のような特徴があります。
- 一筆書きで数字が表されている
- 4の上がくっついている
- 1の下の線はない
- 線が細い
- 構成している線が少ない
これに加えて、管を使っているので少し縦長という特徴もありますが、フォントの縦横比は後で変えられるので、フォント選定の際は考えなくても良さそうです。
この辺の特徴を満たすいい感じのフォントをGoogleFontsで頑張って探しました。線の細さでフィルターすると数が減って探しやすかったです。最終的には下記のフォントを使うことにしました。
光るワイヤーを作る
上で選んだ文字を光らせます。
CSSでネオンっぽく文字を表示する方法は下記のcodepenを参考にさせてもらいました。
CSS animated neon signニキシー管の光は、
- ワイヤー部分の黄色く光る部分
- その周囲の蛍光物質が放電してできたオレンジ色に光る部分
- 光で照らされて淡くオレンジ色に光る部分
の3つの光があるとして、それぞれCSSに書いていきました。
1はテキストのcolorで設定し、2、3はtext-shadowを使って設定しています。text-shadowの色が薄い部分は複数回かけることでいい感じの濃さにしています。また、元のフォントよりも横長にしたかったので
CSSは以下の通りです。@import url('https://fonts.googleapis.com/css2?family=Quicksand:wght@300&display=swap'); .nixie_tube { font-size: 200px; font-family: 'Quicksand', sans-serif; color: #fff256; text-shadow: 0 0 0.05em #d45424, 0 0 0.05em #d45424, 0 0 0.05em #d45524, /* 導線の輪郭 */ 0 0 0.1em #d45424, 0 0 0.1em #d45424, 0 0 0.1em #d45524, /* 導線の輪郭 */ 0 0 0.5em #d45424, 0 0 0.5em #d45424, 0 0 0.5em #d45524, /* 導線の輪郭 */ 0 0 0.5em #d45424, 0 0 0.5em #d45424, 0 0 0.5em #d45524, /* 導線の輪郭 */ 0 0.05em 0.01em #000; position: relative; } .light_up_wire{ position: absolute; top: 0.2em; left: -0.3em; top: -0.15em; transform: scale(0.8, 1.0); }<span class="nixie_tube"> <span class="light_up_wire">{{show_num}}</span> </span>光っていないワイヤーを作る
ニキシー管の中を見ると、数字の形をしたワイヤーが中に入っていることがわかります。
このごちゃっとした感じもニキシー管の良さですよね。というわけで、光っていない部分の数字も作ります。先ほど作ったCSSのcolorを暗くして、text-shadowを消します。
また、positionをabsoluteにすることで、何個表示しても同じ場所に重なるように表示されるようにします。.nixie_wire { color: #34241c; text-shadow: none; position: absolute; transform : scale(0.8, 1.0); }htmlの方は0~9まで全ての数字を書いてしまうと、あまりに重なりすぎてなんだかわからなくなってしまうので、特徴的な数字だけ間引いて入れています。フォントの線も細く出来たら良かったのですが...
<span class="nixie_tube"> <span class="nixie_wire">0</span> <span class="nixie_wire">2</span> <span class="nixie_wire">7</span> <span class="nixie_wire">8</span> <span class="light_up_wire">{{show_num}}</span> </span>ガラス管を作る
ニキシー管は試験管みたいな容器の中に入っているところがかっこいいです。
なので、いい感じの管もCSSで作ります。ニキシー管のガラスの部分と上下のキャップを、指定した範囲の背景にグラデーションを設定し、枠を丸めることで作っています。ガラスの方は基本透明ながらも、オレンジの光が当たって側面が光っているようにしています。キャップの方は金属っぽくなるように上の方から光が当たって白くなるような感じで斜めのグラデーションをかけています。
.nixie_glass { width : 0.55em; height : 1.2em; position: relative; top: 0.18em; opacity: 0.5; background: radial-gradient(transparent 60%, #aa3315); border-radius: 0.15em; display: inline-block; } .nixie_cap { width : 0.6em; height : 0.2em; position: absolute; top: -0.08em; left: -0.025em; background: linear-gradient( transparent 40%, #aa5d31 70%,#111 80%), linear-gradient(to top left, #111 60%, #444 80%); border-radius: 0.08em 0.08em 0.02em 0.02em; display: inline-block; } .nixie_buttom { width : 0.6em; height : 0.2em; position: absolute; top: 0.98em; left: -0.025em; background: linear-gradient(to top, transparent 40%, #aa5d31 70%,#111 80%), linear-gradient(to top left, #111 60%, #444 80%); border-radius: 0.02em 0.02em 0.08em 0.08em; display: inline-block; }ここまで設定すると以下のようになります。
切り替え時の残像を再現する
ここまでで一応数字を表示するところまでできるようになったのですが、まだニキシー管のかっこいいところを再現しきれているとは言えません。ニキシー管はただ数字を表示しているだけでも素敵なのですが、時計などで数字が切り替わるとき、前に表示していた数字がすぐには消えずに残像となって残り、その間に新しい数字が出てくるような挙動をします。
参考動画
このふわっと感をCSSで再現したい...と思ったのですが、CSSで切り替わりのタイミングを制御する方法がわからなかったので、諦めてjavascriptを使いました。フェードインとフェードアウトのアニメーションはCSSのアニメーション機能を使いました。text-shdowとcolorを徐々に変化させるようにしています。これを表示させる数値が切り替わるたびに実行するようにします。
また、フェードインさせる数字とフェードアウトさせる数字の二種類を作り、それぞれ同時にアニメーションさせることで切り替わりを再現しています。.fadein { animation: fadein linear 0.2s; } .fadeout { animation: fadeout linear 0.2s forwards; } @keyframes fadein { 0% { color: #34241c; text-shadow: none; } 100% { color: inherit; text-shadow: inherit; } } @keyframes fadeout { 0% { color: inherit; text-shadow: inherit; } 100% { color: #000; text-shadow: none; } }これでふわっと数字が切り替わる様子が再現できました!
↓ではわかりやすいように実際よりゆっくりと切り替えています。
完成
Vueのコンポーネントとして一つのファイルにまとめました。
これで好きな数字をpropから渡してあげることで、時計やカウンターのような使い方をすることができます。
デモのページでは、このコンポーネントを使ったデモを表示しています。<template> <span class="nixie_tube"> <span class="nixie_wire">0</span> <span class="nixie_wire">2</span> <span class="nixie_wire">7</span> <span class="nixie_wire">8</span> <span v-bind:class="{ fadeout: on_change }" class="nixie_wire">{{previous_num}}</span> <span v-bind:class="{ fadein: on_change }" class="light_up_wire">{{show_num}}</span> <span class="nixie_glass"></span> <span class="nixie_cap"></span> <span class="nixie_buttom"></span> </span> </template> <script> export default { props: { show_num: [String, Number] // ニキシー管に表示する数字 }, data: () => { return{ on_change: false, previous_num: 8 } }, methods:{ change_num: function() { if(this.show_num == this.previous_num){ return } this.on_change = true setTimeout(()=>{ this.on_change = false this.previous_num = this.show_num }, 200) } }, watch:{ show_num: function() { this.change_num() } }, mounted() { this.change_num() } } </script> <style> @import url('https://fonts.googleapis.com/css2?family=Quicksand:wght@300&display=swap'); .nixie_tube { font-size: 200px; font-family: 'Quicksand', sans-serif; color: #fff256; text-shadow: 0 0 0.05em #d45424, 0 0 0.05em #d45424, 0 0 0.05em #d45524, /* 導線の輪郭 */ 0 0 0.1em #d45424, 0 0 0.1em #d45424, 0 0 0.1em #d45524, /* 導線の輪郭 */ 0 0 0.5em #d45424, 0 0 0.5em #d45424, 0 0 0.5em #d45524, /* 導線の輪郭 */ 0 0 0.5em #d45424, 0 0 0.5em #d45424, 0 0 0.5em #d45524, /* 導線の輪郭 */ 0 0.05em 0.01em #000; position: relative; } .light_up_wire{ position: absolute; transform: scale(0.8, 1.0); } .nixie_wire { color: #34241c; text-shadow: none; position: absolute; transform : scale(0.8, 1.0); } .nixie_glass { width : 0.55em; height : 1.2em; position: relative; top: 0.18em; opacity: 0.5; background: radial-gradient(transparent 60%, #aa3315); border-radius: 0.15em; display: inline-block; } .nixie_cap { width : 0.6em; height : 0.2em; position: absolute; top: -0.08em; left: -0.025em; background: linear-gradient( transparent 40%, #aa5d31 70%,#111 80%), linear-gradient(to top left, #111 60%, #444 80%); border-radius: 0.08em 0.08em 0.02em 0.02em; display: inline-block; } .nixie_buttom { width : 0.6em; height : 0.2em; position: absolute; top: 0.98em; left: -0.025em; background: linear-gradient(to top, transparent 40%, #aa5d31 70%,#111 80%), linear-gradient(to top left, #111 60%, #444 80%); border-radius: 0.02em 0.02em 0.08em 0.08em; display: inline-block; } .fadein { animation: fadein linear 0.2s; } .fadeout { animation: fadeout linear 0.2s forwards; } @keyframes fadein { 0% { color: #34241c; text-shadow: none; } 100% { color: inherit; text-shadow: inherit; } } @keyframes fadeout { 0% { color: inherit; text-shadow: inherit; } 100% { color: #000; text-shadow: none; } } </style>おまけ1
ニキシー管で好きな数字を表示できるようにして何をするかって?
それはもちろん...
おまけ2
propには数値だけではなく文字を指定することもできます。
試しにやってみたところ、フォント側からしたらガラス管の幅なんて知ったことではないので容赦なくはみ出してきました。
- 投稿日:2020-05-25T00:46:30+09:00
vuetify+vue/cliによるフロントエンドと、pythonのresponder+sqlite3によるAPIで、SPA(single-page application)のウェブサイトを実装してみたり
はじめに
この前responderとSQLiteとの組み合わせで簡単なウェブサイトを作る例をしました。
-> https://qiita.com/phyblas/items/0f5a7413e6ad4b1685a0今回はresponderとSQLに加えて、SQLデータベースと接続してデータの閲覧や追加や更新や削除したりできるSPA(single-page application)のウェブサイトにします。
フロントエンドは最近とても人気のjavascriptフレームワークであるvuetifyを使います。
vuetifyのコンポーネントは特に設定しなくてもレスポンシブデザインになっているというところは便利です。
pythonの方で使うモジュールは主にsqlite3とresponderとpandasで、サーバとAPIを提供します。
フロントエンドの方では、axiosでAPIからデータを取って、vuetifyでウェブサイトを構成するのです。
vuetifyの中で主役はデータを簡単に表示できるv-data-tableというコンポーネントです。
構造
機能
- ウェブサイトの構造はvuetifyのコンポーネントからなる
- たった1ページしかないSPA(single page application)
- そのページで、データの閲覧と追加と編集と削除を全部やることができる
- サーバはresponderモジュールを含むpythonコード
- データベースはSQLiteで、pythonのsqlite3を使う
- APIはresponderとsqlite3とpandasモジュールからなる
- axiosでAPIからデータを取る
- cssスタイルシートは直接触れる必要なく、全部vuetifyに任せる
- vuetifyのv-data-tableでサイトにデータを表示する
- データの編集と追加はそのページに出るv-dialogで行う
データベース
今回で作るデータベースはアニメキャラの名前と年齢と身長と色(イメージカラー)を収めるテーブルにします
列名 データ型 名前 namae text 年齢 nenrei integer 身長 shinchou real 色 iro text ここで色は#RGBのコード(#789ABCなど)にして、実際にサイトで本当の色に変換できるようにしています。
テーブルを作成するSQLコード
create table kyara (namae text,nenrei integer,shinchou real,iro text,primary key (namae))ファイル
│- app.py # サーバとAPIの実行 │- static # vue/cliで完成したフロントエンドの置くフォルダ │- vueax # vue/cliプロジェクトのコードのフォルダサーバとAPIの方が一つのpythonファイルで実装しますが、フロントエンドの方がvue/cliで色々のファイルからプロジェクトを作ります。詳しくフロントエンドの説明パートで。
環境
今回の実装で使うパソコンの環境
- Mac OS 10.15.4 Catalina
- python 3.7.7
- conda 4.8.3
- responder 2.0.5
- pandas 1.0.3
- axios 0.19.2
- vue/cli 4.3.1
- yarn 1.22.4
使うモジュールについて
vuetifyとvue/cli
vuetifyを使う方法が色々ありますが、ここではvue/cliで構成します。
vuetifyとvue/cliの使い方については色んな記事で説明されているので、ここでは自分が参考にした記事を紹介します。
- Responder + Vue.jsプロジェクト作成手順
- vuetify環境構築とやたら出てくるv-slotについておさらい
- Vuetify2.x でよく使うUIComponents まとめ
- Vuetify 2.0 で datatable を使う
準備
vue/cliはyarnでもnpmでも簡単にインストールできますが、今回はyarnを使います。
yarn global add @vue/cli @vue/cli-service-globalpythonモジュール
pythonモジュールはresponderとsqlite3を使います。詳しい使い方は以下の記事でいっぱい書かれいます。
responder
- Responder + WebSocketで簡易チャットアプリ
- Python+ResponderでWEBアプリケーションを構築する。
- はじめての Responder(Python の次世代 Web フレームワーク)
- 人間のためのイケてるPython WebFramework「responder」、そして作者のKenneth Reitzについて
- responderでjsonを返す際に日本語をUnicodeエスケープさせない
sqlite3
準備
responderとpandasをインストールする必要があるモジュール。
pip install responder pip install pandasサーバ(python)の実装
サーバを実行するapp.pyのコード
コード全体
import sqlite3,responder,os import pandas as pd cors_params={'allow_origins':'http://127.0.0.1:8080/', 'allow_methods':['*'], 'allow_headers': ['*']} api = responder.API(cors=True,cors_params=cors_params) dbfile = 'data.db' api.add_route('/',static=True) @api.route('/db/kyara/') async def index(req,resp): with sqlite3.connect(dbfile) as conn: if(req.method=='get'): resp.headers = {"Content-Type": "application/json; charset=utf-8"} sql_select = ''' select * from kyara ''' # 全てのキャラのデータを表示するSQLコード data = pd.read_sql(sql_select,conn) # データをpandasのデータフレームに読み込んでjsonに変換する resp.text = data.to_json(orient='records',force_ascii=0) elif(req.method=='post'): param = await req.media() # 追加するデータを取得 sql_insert = ''' insert into kyara (namae,nenrei,shinchou,iro) values (:namae,:nenrei,:shinchou,:iro) ''' # 新しいデータ追加 conn.execute(sql_insert,param) @api.route('/db/kyara/{namae}') async def show(req,resp,*,namae): with sqlite3.connect(dbfile) as conn: if(req.method=='get'): resp.headers = {"Content-Type": "application/json; charset=utf-8"} sql_select = ''' select * from kyara where namae==? ''' # その名前を持つキャラのデータを取る kyara = pd.read_sql(sql_select,conn,params=[namae]) resp.text = kyara.iloc[0].to_json(force_ascii=0) elif(req.method in ['patch','post','patch']): param = await req.media() # 更新するデータを取得 param['namae0'] = namae sql_update = ''' update kyara set namae=:namae,nenrei=:nenrei,shinchou=:shinchou,iro=:iro where namae==:namae0 ''' # データ更新 conn.execute(sql_update,param) elif(req.method=='delete'): sql_delete = ''' delete from kyara where namae==? ''' # データ削除 conn.execute(sql_delete,[namae]) if(__name__=='__main__'): # 初めて実行した時、新たにテーブルを作っておく if(not os.path.exists(dbfile)): with sqlite3.connect(dbfile) as conn: sql_create = ''' create table kyara ( namae text, nenrei integer, shinchou real, iro text, primary key (namae) ) ''' conn.execute(sql_create) # サーバ開始 api.run()次は各部分の詳しい説明
ルート
api.add_route('/',static=True)の部分はstatic/index.htmlをデフォルトのルートにするためです。
データベースとやり取りをするためのAPIのルートは2つですが、メソッドの違いによって5つになります
ルート メソッド 機能 /db/kyara/ get 全部データ表示 /db/kyara/ post 追加 /db/kyara/{名前} get 一つずつデータ表示 /db/kyara/{名前} post, patch, put 編集 /db/kyara/{名前} delete 削除 これは大体ruby on railsのresourcesと同じようにしています。
CORSについての話
cors_paramsの設定はCORS(Cross-Origin Resource Sharing)を使う必要があるためです。
vue/cliで開発する時にフロントエンドはhttp://127.0.0.1:8080/をサーバにするので、responderのhttp://127.0.0.1:5042/にデータを取るのはCORSの問題と会うので、APIオブジェクトにhttp://127.0.0.1:8080/からアクセスできるように設定する必要があります。
実用する時にフロントエンドはAPIと同じくresponderのサーバに入るので、CORSは必要なくなって消してもいいです。
SQLとpandas
SQLデータベースに接続してjsonに変換する時にpandasのpd.read_sql()を使うと便利です
jsonに変換する時にto_json()を使ってorient='records'に指定しておくと「データのオブジェクトを列挙するアレイ」という形になります。
APIの試しながらデータを追加してみる
準備が完成したら、次はpythonコードを実行してサーバ開始。
python app.pyこれでAPIの部分は使えるようになるはずです。
そしてブラウザーで http://127.0.0.1:5042/db/kyara にアクセスししみます。
最初はまだ何のデータも入っていないので空っぽ。
ここでAPIの追加機能をテストするついでに、データを準備します。
requestsを使ってpostメソッドで一つずつデータを追加してみます。
import requests nyuuryoku = ''' 高坂穂乃果,16,1.57,#F39800 南ことり,16,1.59,#808080 星空凛,15,1.55,#00B7CE 小泉花陽,15,1.56,#008000 園田海未,16,1.59,#0067C0 東條希,17,1.59,#A757A8 矢澤にこ,17,1.54,#F8ABA6 西木野真姫,15,1.61,#ED1A3D 絢瀬絵里,17,1.62,#AFDFE4 '''.strip().split('\n') retsumei = 'namae,nenrei,shinchou,iro'.split(',') for gyou in nyuuryoku: data = {k:v for k,v in zip(retsumei,gyou.split(','))} url = 'http://127.0.0.1:5042/db/kyara/' r = requests.post(url,data) print(r) # <Response [200]>が出たら問題ないコードを実行した後、もう一度 http://127.0.0.1:5042/db/kyara にアクセスしたら、今回はデータがちゃんと入っていることは確認できます。
これでサーバとAPIの準備が完成です。
フロントエンド(javascript)の実装
vueプロジェクト作成
app.pyとstaticフォルダと同じ場所でvueaxというフォルダを作ってその中でvuetifyのプロジェクトを作成します。
mkdir vueax cd vueax # プロジェクトの置くフォルダへ vue create . # そこでプロジェクト作成作成する時の選択はdefaultを選んでいいです。
Vue CLI v4.3.1 ? Generate project in current directory? (Y/n) y ? Please pick a preset: ❯ default (babel, eslint) Manually select features✨ Creating project in /Users/phyblas/vueres/vueax. ? Initializing git repository... ⚙️ Installing CLI plugins. This might take a while... yarn install v1.22.4 info No lockfile found. [1/4] ? Resolving packages... [2/4] ? Fetching packages... [3/4] ? Linking dependencies... [4/4] ? Building fresh packages... success Saved lockfile. ✨ Done in 54.19s. ? Invoking generators... ? Installing additional dependencies... yarn install v1.22.4 [1/4] ? Resolving packages... [2/4] ? Fetching packages... [3/4] ? Linking dependencies... [4/4] ? Building fresh packages... success Saved lockfile. ✨ Done in 12.83s. ⚓ Running completion hooks... ? Generating README.md... ? Successfully created project vueax. ? Get started with the following commands:そして使うモジュールであるvuetifyとaxiosを追加します。
vuetify追加
vue add vuetify # vuetifyをインストール
ここも選択が出ますが、defaultのままで。
? Installing vue-cli-plugin-vuetify... yarn add v1.22.4 [1/4] ? Resolving packages... [2/4] ? Fetching packages... [3/4] ? Linking dependencies... [4/4] ? Building fresh packages... success Saved lockfile. success Saved 4 new dependencies. info Direct dependencies └─ vue-cli-plugin-vuetify@2.0.5 info All dependencies ├─ interpret@1.2.0 ├─ rechoir@0.6.2 ├─ shelljs@0.8.4 └─ vue-cli-plugin-vuetify@2.0.5 ✨ Done in 9.86s. ✔ Successfully installed plugin: vue-cli-plugin-vuetify ? Choose a preset: (Use arrow keys) ❯ Default (recommended) Prototype (rapid development) Configure (advanced) ? Invoking generator for vue-cli-plugin-vuetify... ? Installing additional dependencies... yarn install v1.22.4 [1/4] ? Resolving packages... [2/4] ? Fetching packages... [3/4] ? Linking dependencies... [4/4] ? Building fresh packages... success Saved lockfile. ✨ Done in 13.37s. ⚓ Running completion hooks... ✔ Successfully invoked generator for plugin: vue-cli-plugin-vuetify vuetify Discord community: https://community.vuetifyjs.com vuetify Github: https://github.com/vuetifyjs/vuetify vuetify Support Vuetify: https://github.com/sponsors/johnleideraxios追加
yarn add axios vue-axios # axiosをインストール
yarn add v1.22.4 [1/4] ? Resolving packages... [2/4] ? Fetching packages... [3/4] ? Linking dependencies... warning " > sass-loader@8.0.2" has unmet peer dependency "webpack@^4.36.0 || ^5.0.0". warning " > vuetify-loader@1.4.3" has unmet peer dependency "webpack@^4.0.0". [4/4] ? Building fresh packages... success Saved lockfile. success Saved 3 new dependencies. info Direct dependencies ├─ axios@0.19.2 └─ vue-axios@2.1.5 info All dependencies ├─ axios@0.19.2 ├─ follow-redirects@1.5.10 └─ vue-axios@2.1.5 ✨ Done in 5.13s.開発段階のvueとjsコード
プロジェクトを作成したらプロジェクトのフォルダの中はこうなるでしょう。
大体のファイルはそのままで、変えるのはこれだけ
- ./src/App.vue
./src/components/HelloWorld.vue>> ./src/components/index.vue- ./src/plugins/vuetify.js
- ./vue.config.js
vue.config.jsは開発の時はそのままで良いですが、実用する時にちょっと設定の変更が必要となります。
src/plugins/vuetify.js
機能の追加を設定するのはsrc/plugins/vuetify.jsファイルです。ここで日本語化の設定をして、色のテーマを3つ(qiitaの緑, twitterの青, gmailの赤)追加します。(これについてこの記事でも参考 https://qiita.com/azukiazusa/items/c364f81d90c7734ba2e6 )
import Vue from 'vue'; import Vuetify from 'vuetify/lib'; import ja from "vuetify/src/locale/ja.ts"; Vue.use(Vuetify); export default new Vuetify({ lang: { locales: { ja }, current: "ja" }, theme: { dark: true, themes: { dark: { qiita: "#55c500", twitter: "#1da1f2", gmail: "#B23121" }, }, }, });src/App.vue
ウェブサイトの構造はApp.vueに書かれます。
<template> <v-app> <v-app-bar app color="qiita" class="title"> ṿữểţĩƒỹ<v-spacer/>~~<v-spacer/> řệşṕồñđễř<v-spacer/>μ's<v-spacer/> <v-btn href="https://qiita.com/phyblas" target="_blank" text > <v-icon>mdi-open-in-new</v-icon> </v-btn> </v-app-bar> <v-content> <v-card> <index/> </v-card> </v-content> </v-app> </template> <script> import index from './components/index'; export default { name: 'App', components: { index, } }; </script>全部はv-appに囲まれるのが決まりで、中にはv-app-barとv-contentに分けられます。
v-app-barはページの一番上の部分で、ここでは簡単にあまり何も置かないことにします。
v-contentの部分はコンポーネントを使っています。
の部分はsrc/components/index.vueに書いたコンポーネントに変わります。
そのためにscript部分の中で'./components/index'をimportしてコンポーネントに指定する必要があります。
src/components/index.vue
もともとsrc/componentsのフォルダの中にはHelloWorld.vueというファイルがあったが、index.vueにします。
普通はvueのアプリは色んなコンポーネントから成すことができますが、今回は小さな簡単なウェブサイトで、細かく分けずにコンポーネントを一つのファイルにします。
<template> <v-container> <v-row class="text-center"> <v-data-table :headers="header" :items="kyaradata" :items-per-page="4" :footer-props="{itemsPerPageOptions: [4, 8, -1]}" > <template #item.namae="{ item: { namae,iro } }"> <v-card class="title d-flex justify-space-between" :style="{background: iro}"> <v-card v-for="(s,i) in namae" :key="i" style="margin: 3px">{{s}}</v-card> </v-card> </template> <template #item.nenrei="{ item: { nenrei } }"> {{nenrei}} 歳 </template> <template #item.shinchou="{ item: { shinchou } }"> {{shinchou*100}} cm </template> <template #item.iro="{ item: { iro } }"> <v-card class="headline d-flex justify-center" :style="{'color': iro}">■</v-card> </template> <template #item.hoka="{ item: k }"> <v-btn color="twitter" @click="henshuu(k)">編集</v-btn> <v-btn color="gmail" @click="sakujo(k)">削除</v-btn> </template> </v-data-table> </v-row> <v-container> <v-btn color="twitter" dark fab class="title" width="120" @click="tsuika">追加 +</v-btn> <v-btn color="gmail" dark fab class="title" width="120" @click="zenmetsu">全部削除</v-btn> </v-container> <v-dialog v-model="henshuuchuu" max-width="50%"> <v-card color="twitter"> <v-card-title> データ <span class="headline" v-if="i_henshuu==-1">追加 +</span> <span class="headline" v-else>編集</span> </v-card-title> <v-card-text> <v-container> <v-card v-for="k in header.slice(0,4)" :key="k.value"> <v-text-field v-model="sonokyara[k.value]" :label="k.text" style="margin: 5px"></v-text-field> </v-card> </v-container> </v-card-text> <v-card-actions> <v-spacer></v-spacer> <v-btn class="title" color="qiita" @click="hozon">保存</v-btn> <v-btn class="title" color="gmail" @click="tojiru">取消</v-btn> </v-card-actions> </v-card> </v-dialog> </v-container> </template> <script> import axios from 'axios'; // APIからのデータを取るためにaxiosが必要 export default { name: 'index', data: () => ({ kyaradata: [], // APIから取るキャラのデータはここに置く sonokyara: {}, // 編集するキャラのデータ header: [ // テーブルの列の構造 {text: "名前", value: "namae"}, {text: "年齢", value: "nenrei"}, {text: "身長", value: "shinchou"}, {text: "色", value: "iro", sortable: false}, {value: "hoka", sortable: false}, // 編集と削除のボタンのある列 ], henshuuchuu: false, // 編集開始したらtrueになる i_henshuu: -1, // 編集しているデータのインデックス }), methods: { henshuu(item) { // 編集ボタンを押すと編集フォームのv-dialogが現れる this.i_henshuu = this.kyaradata.indexOf(item); this.sonokyara = Object.assign({},item); this.henshuuchuu = true; }, tsuika(){ // 追加ボタンを押すと同じく編集フォームも現れるが「追加」モードになる this.i_henshuu = -1; this.sonokyara = {namae: "", nenrei: 0, shinchou: 0, iro: "#FFFFFF"}; // 最初の値 this.henshuuchuu = true; }, tojiru(){ // 取消ボタンを押すと編集フォームは消える this.henshuuchuu = false; this.sonokyara = {}; this.i_henshuu = -1; }, async hozon(){ // 保存ボタンを押すとデータが更新してデータベースに保存される if(this.i_henshuu==-1){ // 追加の場合 let url = "http://127.0.0.1:5042/db/kyara/"; let r = await axios.post(url,this.sonokyara); // APIを通じてデータを追加 console.log(r); this.kyaradata.push(this.sonokyara); // テーブルにもデータ追加 this.tojiru(); // フォームを閉じる } else{ // 編集の場合 let url = "http://127.0.0.1:5042/db/kyara/" + encodeURIComponent(this.kyaradata[this.i_henshuu].namae); let r = await axios.patch(url, this.sonokyara); // APIを通じてデータを更新 console.log(r); Object.assign(this.kyaradata[this.i_henshuu],this.sonokyara); // テーブルの中のデータも更新 this.tojiru(); // フォームを閉じる } }, async sakujo(item) { // 取消ボタンを押すと if(confirm('消していいのか?')){ // まずは本当に消すかどうか確認してから let url = "http://127.0.0.1:5042/db/kyara/" + encodeURIComponent(item.namae); let r = await axios.delete(url); // APIを通じてデータを消す console.log(r); let i = this.kyaradata.indexOf(item); this.kyaradata.splice(i,1); // テーブルからもデータを消す } }, async zenmetsu() { // 全部削除ボタンを押すと、データを全て消される if(confirm('本当に全部消していいわけ?')){ for (let k of this.kyaradata){ let url = "http://127.0.0.1:5042/db/kyara/" + encodeURIComponent(k.namae); let r = await axios.delete(url); // APIを通じて一つずつデータを消す console.log(r); } this.kyaradata = []; // 空っぽのテーブル } } }, async created(){ // 最初の時にAPIを通じてキャラのデータを取得する this.kyaradata = (await axios.get("http://127.0.0.1:5042/db/kyara/")).data; console.log(this.kyaradata); }, } </script>index.vueの中で前半はhtmlのtemplateで、後半はjavascriptです。
templateの部分は大体3つに分けられます。
- 全部のデータを列挙するv-data-table
- 追加と全部削除のポタン
- 編集や追加をするフォームを持つv-dialog(最初は非表示にしていて、使う時に表示される)
テーブルの列は5つ
- 名前
- 年齢
- 身長
- 色
- 編集と削除のボタン
名前はclassをd-flexとjustify-space-betweenに設定して文字の間隔は広げられる。
身長はデータの中ではメートルだけど、ここではcmにする。
色はコードを色にして四角を描く。
最後の列はデータを表示するのではなくデータの編集と削除をするためのボタンの置かれるところ。
開発段階のサーバ
開発している時vue/cliは http://127.0.0.1:8080/ でサーバを作ります。
vue/cliプロジェクトのフォルダでserveコマンドでサーバ開始
yarn servehttp://127.0.0.1:8080/ へアクセスして上手く動けることを確認できます。
実用段階
コードが完成したら次は実用のための準備。
vue.config.jsを変更してbuildコマンドで実用のサイトを作成します。
buildする直前にちょっとvue.config.jsの中のpublicPathとoutputDirの設定を変える必要があります。
vue.config.jsの設定変更
module.exports = { "transpileDependencies": [ "vuetify" ], publicPath: "../static", outputDir: "../static" }これによってbuildを実行する時に/staticフォルダにファイルが作成されます。パスも同じくあそこに設定されます。
defaultでは /distというフォルダに置かれますが、その後でstaticに移動しても動かないので、最初からstaticでbuiltするように設定する必要があるらしいです。
build
設定を変更したら、build実行。
yarn build最後に全部のファイルはこうなります。
実行と結果
全て準備完成したら、サイトにアクセスしてみます。
サーバの方が先にさっきの段階で開始してテストしてみましたが、その後で追加したのはstaticの部分でpythonコードに変化はないのでサーバを閉じて再びの実行する必要がないでしょう。
でも一応サーバを再開した方がいいかも。
python app.pyブラウザーで http://127.0.0.1:5042/ にアクセスして確認。
普段に600pxより広いウィンドウで見る場合はこのように見えます。
試して600pxより小さくしたらこうなります。
これはvuetifyのv-data-tableの最初から持っている機能です。特に何の設定する必要もなく、自動的にレスポンシブになっているので、便利です。
それと、各列による並び替えの機能もついているので、試して身長の列のヘッダをクリックして身長でソートすることができます。
そして下の方にはページあたりの行数を選択して変更することもできます。
これはv-data-tableのfooter-propsプロパティで設定されたitemsPerPageOptionsを[4, 8, -1]にしたから選べる行数は4と8とすべて(-1はすべてという意味)になります。
追加ポタンをクリックしたらフォームが出て、データを追加することができます。
同じように、一つの編集ポタンをクリックしてみたら、そのキャラのデータを編集するフォームが出て、保存ポタンを押したらデータは更新します。
削除のボタンをクリックしたら本当に消すかどうかを確認するウィンドウが出ます。
全部削除のボタンを押してOKしたらデータは全部消されます。
これで大体は上手く動けるようになっていることは確認できました。
終わりに
以上vuetifyとresponderで簡単なSPAのウェブサイトを作ってみました。
サーバとAPIはresponderで簡単に作れるし、フロントエンドの方はvuetifyのコンポーネントを使うと色々便利で簡単になります。