- 投稿日:2019-05-27T22:09:36+09:00
Laravel Mix の webpack.mix.js を Storybook でも利用する
はじめに
Atomic Design ~堅牢で使いやすいUIを効率良く設計する 、この本があまりに素晴らしくて、写経をしたいので、その準備として Storybook を利用できるようにしました。
Storybook を導入したのですが、Laravel Mix で設定した CSS Modules が適用されていませんでした。
Storybook へ Laravel Mix の webpack.mix.js を取り込む方法がわかったので、記録に残します。
Storybook の webpack.config.js を用意
デフォルトで Storybook から読み込まれる .storybook の下に webpack.config.js を作成します。
ディレクトリ構成は以下のようになります。
.storybook/ addons.js config.js webpack.config.jswebpack.config.js は以下の内容です。
laravel-mix の webpack.config.js を require しているのがポイントです。const path = require('path'); // your app's webpack.config.js const custom = require('../node_modules/laravel-mix/setup/webpack.config.js'); module.exports = async ({ config, mode }) => { return { ...config, module: { ...config.module, rules: custom.module.rules } }; };これで完了です。
これで、 webpack.mix.js の設定内容が Storybook にも反映されているはずです!!さいごに
ドキュメントを読めばなんとかなるもんだ!
これで Atomic Design ~堅牢で使いやすいUIを効率良く設計する の写経ができるぞ。
参考
- https://storybook.js.org/docs/configurations/custom-webpack-config/#using-your-existing-config
- 投稿日:2019-05-27T21:45:52+09:00
[Vue.js] 別ウィンドウで開くリンクの作成
やりたいこと
別ウィンドウで開くリンクをつくりたかった
方法
メインのコンポーネント
<script> import { ROUTER } from '@/libs/constant-router' export default { name: 'Register', methods: { toTerms() { let routeData = this.$router.resolve({name: ROUTER.TERMS_NAME, query: {data: "someData"}}); window.open(routeData.href, '_blank'); //←★ここ }, } </script>設定ファイル群
constant-router.js
プロジェクトルート直下に「libs」と名付けたディレクトリを切って、その中に格納
export const ROUTER = { TERMS_PATH: '/terms', TERMS_NAME: 'terms', }router.js
import Terms from '@/views/Terms.vue' Vue.use(Router) const router = new Router({ mode: 'history', base: process.env.BASE_URL, routes: [ { path: ROUTER.TERMS_PATH, name: ROUTER.TERMS_NAME, component: Terms, }, ], }) router.toTerms = () => { router.push({ name: ROUTER.TERMS_NAME }) } export default router
- 投稿日:2019-05-27T19:19:36+09:00
vue-chartjsでoptionsを動的に変更させる方法
optionsに関してはレスポンシブに変更されないみたいで
this.renderChart()
で明示的に再描画する必要があるみたいです1。
チャートのデータ変更はレスポンシブに変更されます。
- 投稿日:2019-05-27T16:23:59+09:00
vue.js 現在のパスに応じて表示したり隠したり
make ページでは メニュー要素を隠したい。
そんなときは以下のように書く。vue.blade.php<el-footer id="footer" class="layout-footer" v-show="$route.path.indexOf('make') < 0"> こんな塩梅 </el-footer>これで現在のページが make にマッチしない時のみ メニューが表示されるようになる。
- 投稿日:2019-05-27T14:40:22+09:00
Nuxt v2.7 で実行時に「TypeError: fsevents.watch is not a function」のエラーで起動できない
Nuxt v2.7.1にしたら、起動できなくなった
Nuxt v2.7.x で、yarn dev 実行時に以下のエラーが出た。
fsevents-handler.jsconst stop = fsevents.watch(path, callback); ^ TypeError: fsevents.watch is not a function at createFSEventsInstance (/Users/masaakikakimoto/Documents/nuxt_firebase_sns/node_modules/@nuxt/builder/node_modules/chokidar/lib/fsevents-handler.js:57:25) at setFSEventsListener (/Users/masaakikakimoto/Documents/nuxt_firebase_sns/node_modules/@nuxt/builder/node_modules/chokidar/lib/fsevents-handler.js:111:16) at FsEventsHandler._watchWithFsEvents (/Users/masaakikakimoto/Documents/nuxt_firebase_sns/node_modules/@nuxt/builder/node_modules/chokidar/lib/fsevents-handler.js:296:18) at initWatch (/Users/masaakikakimoto/Documents/nuxt_firebase_sns/node_modules/@nuxt/builder/node_modules/chokidar/lib/fsevents-handler.js:433:27) at LOOP (fs.js:1787:14) at process._tickCallback (internal/process/next_tick.js:176:11) npm ERR! code ELIFECYCLE npm ERR! errno 1調べると以下のIsuueがHit
https://github.com/nuxt/nuxt.js/issues/5725nodejs のバージョンを下げることで解決した。
とりあえずnode v10.15.3でNuxtが正常起動することを確認しました。
(※v12系やv9系では、エラーが出るようだ。)
※追記 おまけ
気を良くして、いろいろとパッケージを追加していたら、またまたNuxtが起動しなくなった。
エラー内容
ERROR Failed to compile with 1 errors friendly-errors 17:07:19 This dependency was not found: friendly-errors 17:07:19 friendly-errors 17:07:19 * core-js/modules/es7.promise.finally in ./.nuxt/client.js friendly-errors 17:07:19 friendly-errors 17:07:19 To install it, you can run: npm install --save core-js/modules/es7.promise.finallycorejs v2.6.5を入れ直すことで解決した。
yarn add core-js@latest2.6.5
- 投稿日:2019-05-27T12:29:16+09:00
ニートのプログラミング未経験者がRailsとVueでTodoアプリを作ってみた
はじめに
Vuejs と Rails API を使って Todo アプリを作りました。
まずは、ローカル環境で動かし
最終的に Heroku へデプロイするところまで書きました。最初に作ったものを載せておきます。
デモ
https://vue-rails-api-todo.herokuapp.com/
コード
https://github.com/youbeer/vue-rails-api-todo
ディレクトリ構成
frontend ディレクトリに Vue のファイルをまとめてあります
vue-rails-api-todo/ ├── app │ ├── channels │ ├── controllers │ ├── jobs │ ├── mailers │ ├── models │ └── views ├── bin ├── config ├── db ├── docs ├── frontend │ ├── dist │ ├── node_modules │ ├── public │ └── src ├── lib ├── log ├── public ├── storage ├── test ├── tmp └── vendor対象読者(こんな方に読んでいただけたら)
Rails と Vue のチュートリアルを勉強してなにか作ってみたい方
事前準備
Rails と VueCLI3 のインストールを行なってください
自分の環境です
Mac MoJava
ruby 2.6.1
Rails 5.2.3
Vue 3.7.0【Rails】サーバーサイドの作成
Rails プロジェクトを API モードで作る
terminalrails new vue-rails-api-todo --api
Gemfile を修正
Gemfilesource 'https://rubygems.org' git_source(:github) { |repo| "https://github.com/#{repo}.git" } ruby '2.6.1' gem 'bootsnap', '>= 1.1.0', require: false gem 'puma', '~> 3.11' gem 'rack-cors' gem 'rails', '~> 5.2.3' gem 'tzinfo-data', platforms: %i[mingw mswin x64_mingw jruby] group :development, :test do gem 'byebug', platforms: %i[mri mingw x64_mingw] gem 'sqlite3' end group :development do gem 'listen', '>= 3.0.5', '< 3.2' gem 'pry-byebug' gem 'pry-doc' gem 'pry-rails' gem 'pry-stack_explorer' gem 'spring' gem 'spring-watcher-listen', '~> 2.0.0' end group :production do gem 'pg' endGem をインストール
terminalbundle install
Model を作る
フィールドは2つだけです
- title: タスクの内容
- completed: 完了・未完了
terminalrails g model Todo title:string completed:booleanmigration ファイルの修正
Not Null 制約 と デフォルト値を追記してます
db/migrate/20190525063511_create_tasks.rbclass CreateTodos < ActiveRecord::Migration[5.2] def change create_table :todos do |t| t.string :title, null: false t.boolean :completed, default: false, null: false t.timestamps end end endマイグレーション
terminalrails g model Task title:string completed:booleanModel にバリデーションを追加
app/models/todo.rbclass Todo < ApplicationRecord validates :title, presence: true endルーティングの修正
resources :todos, except: :show
以外に2つルーティングを追加しました
patch 'check_all', to: 'todos#check_all'
: タスクの完了・未完了delete 'delete_completed', to: 'todos#delete_completed'
: 完了タスクを全削除config/routes.rbRails.application.routes.draw do root 'api/v1/todos#index' namespace :api do namespace :v1, format: :json do patch 'check_all', to: 'todos#check_all' delete 'delete_completed', to: 'todos#delete_completed' resources :todos, except: :show end end endルーティングは詳細は、こんな感じです
terminalPrefix Verb URI Pattern Controller#Action root GET / api/v1/todos#index api_v1_check_all PATCH /api/v1/check_all(.:format) api/v1/todos#check_all api_v1_delete_completed DELETE /api/v1/delete_completed(.:format) api/v1/todos#delete_completed api_v1_todos GET /api/v1/todos(.:format) api/v1/todos#index POST /api/v1/todos(.:format) api/v1/todos#create api_v1_todo PATCH /api/v1/todos/:id(.:format) api/v1/todos#update PUT /api/v1/todos/:id(.:format) api/v1/todos#update DELETE /api/v1/todos/:id(.:format) api/v1/todos#destroy rails_service_blob GET /rails/active_storage/blobs/:signed_id/*filename(.:format) active_storage/blobs#show rails_blob_representation GET /rails/active_storage/representations/:signed_blob_id/:variation_key/*filename(.:format) active_storage/representations#show rails_disk_service GET /rails/active_storage/disk/:encoded_key/*filename(.:format) active_storage/disk#show update_rails_disk_service PUT /rails/active_storage/disk/:encoded_token(.:format) active_storage/disk#update rails_direct_uploads POST /rails/active_storage/direct_uploads(.:format) active_storage/direct_uploads#createcontroller の用意
terminalrails g controller api::v1::todoscontroller を修正
app/controllers/api/v1/todos_controller.rbclass Api::V1::TodosController < ApplicationController before_action :set_todo, only: %i[show update destroy] # GET api/vi/todos/ def index @todos = Todo.all.order(created_at: :asc) render json: @todos end # Post api/vi/todos def create @todo = Todo.new(todo_params) if @todo.save render json: @todo else render json: { status: 'error', data: @todo.errors } end end # Put api/vi/todos/:id def update if @todo.update(todo_params) render json: @todo else render json: { status: 'error', data: @todo.errors } end end # Delete api/vi/todos/:id def destroy @todo.destroy render json: @todo end # Delete api/vi/delete_completed def delete_completed todo = Todo.where(completed: true).delete_all render json: todo end # Put api/vi/check_all def check_all todo = Todo.update_all(completed: params['checked']) render json: todo end private def todo_params params.require(:todo).permit(:title, :completed) end def set_todo @todo = Todo.find(params[:id]) end endcors の設定ファイルを修正
Vue 側からのアクセスを許可するため追記
origins 'http://localhost:8080'config/initializers/cors.rbRails.application.config.middleware.insert_before 0, Rack::Cors do allow do origins 'http://localhost:8080' resource '*', headers: :any, methods: %i[get post put patch delete options head] end endseed ファイルを修正
テストデータ作成用
db/seeds.rb10.times do |i| Todo.create(title: "title No#{i + 1}", completed: i.even?) endpostman で確認してみる
terminalrails s rails db:seedPostman をインストールされていない方は、こちらからインストールしてください
- プルダウンから GET を選択し http://localhost:3000/api/v1/todos を入力
- Send をクリック
- テストデータの json が返ってくることを確認
時間がある方はその他のアクションも試してみてください
やり方は、ここでは割愛します【Vue】フロントエンドの作成
プロジェクトを作成
Rails プロジェクトの直下に frontend という名前で Vue プロジェクトを作成します
terminalvue create frontendいくつか質問がでてくるので
- Manually select features を選択肢し
- Vuex を追加してください
その他はお好みでどうぞ
terminal? Please pick a preset: default (babel, eslint) ❯ Manually select featuresterminal? Check the features needed for your project: ◉ Babel ◯ TypeScript ◯ Progressive Web App (PWA) Support ◯ Router ❯◉ Vuex ◯ CSS Pre-processors ◉ Linter / Formatter ◯ Unit Testing ◯ E2E Testingインストールが完了したら fronend へ移動しサーバを起動してみましょう
terminalcd frontend && yarn serveブラウザから http://localhost:8080/ へアクセスし
こんな画面が表示されたら成功です。Bootstrap と axios を追加
- BootstrapVue: Vue 用の Bootstrap モジュール
- axios: 今時の ajax モジュール
terminalyarn add bootstrap-vue bootstrap axiosbootstrap の設定を追加
bootstrap を使うため main.js に追記
import BootstrapVue from "bootstrap-vue"; import "bootstrap/dist/css/bootstrap.css"; import "bootstrap-vue/dist/bootstrap-vue.css"; Vue.use(BootstrapVue);frontend/src/main.jsimport Vue from "vue"; import App from "./App.vue"; import store from "./store"; import BootstrapVue from "bootstrap-vue"; import "bootstrap/dist/css/bootstrap.css"; import "bootstrap-vue/dist/bootstrap-vue.css"; Vue.use(BootstrapVue); Vue.config.productionTip = false; new Vue({ store, render: h => h(App) }).$mount("#app");store を編集
frontend/src/store.jsimport Vue from "vue"; import Vuex from "vuex"; import axios from "axios"; Vue.use(Vuex); const http = axios.create({ baseURL: process.env.NODE_ENV === "development" ? "http://localhost:3000/" : "/", headers: { "Content-Type": "application/json", "X-Requested-With": "XMLHttpRequest" }, responseType: "json" }); export default new Vuex.Store({ state: { filter: "all", todos: [] }, getters: { remaining(state) { return state.todos.filter(todo => !todo.completed).length; }, completedAll(state, getters) { return getters.remaining === 0; }, todosFiltered(state) { if (state.filter === "all") { return state.todos; } else if (state.filter === "active") { return state.todos.filter(todo => !todo.completed); } else if (state.filter === "completed") { return state.todos.filter(todo => todo.completed); } return state.todos; }, showClearCompletedButton(state) { return state.todos.filter(todo => todo.completed).length > 0; } }, mutations: { addTodo(state, todo) { state.todos.push({ id: todo.id, title: todo.title, completed: false, editing: false }); }, clearCompleted(state) { state.todos = state.todos.filter(todo => !todo.completed); }, updateFilter(state, filter) { state.filter = filter; }, checkAll(state, checked) { state.todos.forEach(todo => { todo.completed = checked; }); }, deleteTodo(state, id) { const index = state.todos.findIndex(todo => todo.id === id); state.todos.splice(index, 1); }, updateTodo(state, todo) { const index = state.todos.findIndex(item => item.id === todo.id); state.todos.splice(index, 1, { id: todo.id, title: todo.title, completed: todo.completed, editing: todo.editing }); }, retrieveTodos(state, todos) { state.todos = todos; } }, actions: { retrieveTodos({ commit }) { http .get("/api/v1/todos") .then(response => { commit("retrieveTodos", response.data); }) .catch(error => { console.log(error); }); }, addTodo({ commit }, todo) { http .post("/api/v1/todos", { title: todo.title, completed: false }) .then(response => { commit("addTodo", response.data); }) .catch(error => { console.log(error); }); }, clearCompleted({ commit }) { http .delete("/api/v1/delete_completed") .then(response => { commit("clearCompleted", response.data); }) .catch(error => { console.log(error); }); }, checkAll({ commit }, checked) { http .patch("/api/v1/check_all", { checked }) .then(() => { commit("checkAll", checked); }) .catch(error => { console.log(error); }); }, deleteTodo({ commit }, id) { http .delete(`/api/v1/todos/${id}`) .then(response => { commit("deleteTodo", response.data.id); }) .catch(error => { console.log(error); }); }, updateTodo({ commit }, todo) { http .patch(`/api/v1/todos/${todo.id}`, { title: todo.title, completed: todo.completed }) .then(response => { commit("updateTodo", response.data); }) .catch(error => { console.log(error); }); } } });store は大きく5つのブロックに分かれています
axios のデフォルト通信設定
- baseURL: API 取得のための URL
- header:リクエスト時のヘッダの値
- responseType:レスポンスの形式
import axios from "axios"; Vue.use(Vuex); const http = axios.create({ baseURL: process.env.NODE_ENV === "development" ? "http://localhost:3000/" : "/", headers: { "Content-Type": "application/json", "X-Requested-With": "XMLHttpRequest" }, responseType: "json" });state
アプリの状態管理をするための単一オブジェクトです
- todo: タスクの配列
- filter:全て ・ 完了 ・ 未完了 のフィルタ
state: { filter: "all", todos: [] },getters
component でいう computed にあたります
- remaining: タスク完了の件数
- showClearCompletedButton: クリアボタンを 表示 ・ 非表示 の切替用
getters: { remaining(state) { return state.todos.filter(todo => !todo.completed).length; }, /*************** 省略 ***************/ showClearCompletedButton(state) { return state.todos.filter(todo => todo.completed).length > 0; } }mutaition
state を変更するためのメソッド群です
action を経由して state を更新するために使っています
- addTodo: 新しいタスクを追加しています
- retrieveTodos: ページに最初にアクセスしたとき、タスク一覧を作成しています
mutations: { addTodo(state, todo) { state.todos.push({ id: todo.id, title: todo.title, completed: false, editing: false }); }, /*************** 省略 ***************/ retrieveTodos(state, todos) { state.todos = todos; } },actions
非同期処理を行うためのメソッド群です
axios を使って Rails API を取得するために使っています
- retrieveTodos: Rails の API からタスク一覧を取得しています
- updateTodo: Rails の API からタスクの更新結果を取得しています
actions: { retrieveTodos({ commit }) { http .get("/api/v1/todos") .then(response => { commit("retrieveTodos", response.data); }) .catch(error => { console.log(error); }); }, /*************** 省略 ***************/ updateTodo({ commit }, todo) { http .patch(`/api/v1/todos/${todo.id}`, { title: todo.title, completed: todo.completed }) .then(response => { commit("updateTodo", response.data); }) .catch(error => { console.log(error); }); } }App を編集
frontend/src/App.vue<template> <div id="app" class="container"> <img alt="Vue logo" src="./assets/logo.png" class="logo" /> <h1>VueTODO</h1> <todo-list></todo-list> </div> </template> <script> import TodoList from "./components/TodoList.vue"; export default { name: "App", components: { TodoList } }; </script> <style lang="scss" scoped> .logo { margin: 0 auto; display: block; } </style>メインとなる TodoList コンポーネントを呼び出しています
import TodoList from "./components/TodoList.vue"; export default { name: "App", components: { TodoList } };TodoList コンポーネントを作成
新しいタスクの追加 と 子コンポーネントを束ねています
frontend/src/components/TodoList.vue<template> <div> <b-container class="bv-example-row"> <b-row> <b-col cols="12"> <b-form @submit.prevent="addTodo"> <b-form-group label="New todo" label-for="new-todo"> <b-form-input id="new-todo" v-model="newTodo" placeholder="What needs to be done?" ></b-form-input> </b-form-group> </b-form> <b-list-group> <transition-group name="fade"> <TodoItem v-for="(todo, index) in todosFiltered" :key="todo.id" :todo="todo" :index="index" class="todo-item" :check-all="completedAll" /> </transition-group> </b-list-group> <b-list-group class="mt-4"> <b-list-group-item class="flex-wrap d-flex justify-content-around align-items-center" > <TodoCheckAll /> <TodoItemsRemaining /> </b-list-group-item> <b-list-group-item class="flex-wrap d-flex justify-content-around align-items-center" > <TodoFiltered /> <TodoClearCompleted /> </b-list-group-item> </b-list-group> </b-col> </b-row> </b-container> </div> </template> <script> import TodoItem from "@/components/TodoItem"; import TodoItemsRemaining from "@/components/TodoItemsRemaining"; import TodoCheckAll from "@/components/TodoCheckAll"; import TodoFiltered from "@/components/TodoFiltered"; import TodoClearCompleted from "@/components/TodoClearCompleted"; import { mapGetters } from "vuex"; export default { name: "TodoList", components: { TodoItem, TodoItemsRemaining, TodoCheckAll, TodoFiltered, TodoClearCompleted }, data() { return { newTodo: "" }; }, computed: { ...mapGetters(["completedAll", "todosFiltered"]) }, created() { this.$store.dispatch("retrieveTodos"); }, methods: { addTodo() { if (this.newTodo.trim()) { this.$store.dispatch("addTodo", { id: this.idForTodo, title: this.newTodo }); } this.newTodo = ""; } } }; </script> <style lang="scss"> .fade-enter-active, .fade-leave-active { transition: opacity 0.5s; } .fade-enter, .fade-leave-to { opacity: 0; } </style>TodoItem コンポーネントを作成
親コンポーネントの TodoList から props を受け取り
個々のタスクの表示させていますfrontend/src/components/TodoItem.vue<template> <b-list-group-item class="flex-wrap d-flex justify-content-around align-items-center todo-item" > <b-col cols="2"> <b-form-checkbox v-model="completed" @input="doneEdit"></b-form-checkbox> </b-col> <b-col cols="8"> <label v-if="!editing" :class="{ completed: completed }" @dblclick="editing = true" >{{ title }}</label > <b-form-input v-else v-model="title" v-focus type="text" @blur="doneEdit" @keyup.enter="doneEdit" @keyup.escape="cancelEdit" /> </b-col> <b-col cols="2"> <button type="button" class="close" aria-label="Close" @click="deleteTodo(todo.id)" > <span aria-hidden="true">×</span> </button> </b-col> </b-list-group-item> </template> <script> import { mapActions } from "vuex"; export default { name: "TodoItem", directives: { focus: { inserted: function(el) { el.focus(); } } }, props: { todo: { type: Object, required: true }, index: { type: Number, required: true }, checkAll: { type: Boolean, required: true } }, data() { return { id: this.todo.id, title: this.todo.title, completed: this.todo.completed, editing: false }; }, watch: { checkAll() { this.completed = this.checkAll ? true : this.todo.completed; } }, methods: { ...mapActions(["deleteTodo", "updateTodo"]), doneEdit() { this.editing = false; this.updateTodo({ id: this.id, title: this.title, completed: this.completed, editing: this.editing }); }, cancelEdit() { this.title = this.todo.title; this.editing = false; } } }; </script> <style lang="scss" scoped> .todo-item { animation-duration: 0.3s; } .completed { text-decoration: line-through; color: grey; } </style>TodoItemsRemaining コンポーネントを作成
残りのタスク件数を表示させています
frontend/src/components/TodoItemsRemaining.vue<template> <b-col cols="6"> <span class="text-danger">{{ remaining }}</span> {{ remaining | pluralize("item") }} left </b-col> </template> <script> import { mapGetters } from "vuex"; export default { name: "TodoItemsRemaining", filters: { pluralize: (n, w) => (n === 1 ? w : w + "s") }, computed: { ...mapGetters(["remaining"]) } }; </script>TodoFiltered コンポーネントを作成
All(全て) ・ Active(未完了) ・ Completed(完了)
の値によってタスクにフィルタをかけていますfrontend/src/components/TodoFiltered.vue<template> <b-col cols="6"> <b-form-radio-group v-model="selected" :options="options" buttons button-variant="outline-primary" name="radio-btn-outline" @change="updateFilter" ></b-form-radio-group> </b-col> </template> <script> import { mapState, mapMutations } from "vuex"; export default { name: "TodoFiltered", data() { return { selected: "all", options: [ { text: "All", value: "all" }, { text: "Active", value: "active" }, { text: "Completed", value: "completed" } ] }; }, computed: { ...mapState(["filter"]) }, methods: { ...mapMutations(["updateFilter"]) } }; </script>TodoClearCompleted コンポーネントを作成
完了したタスクの一括クリアボタンを表示させています
frontend/src/components/TodoClearCompleted.vue<template> <b-col cols="6"> <div> <b-button v-if="showClearCompletedButton" variant="outline-primary" @click="clearCompleted" >Clear Completed</b-button > </div> </b-col> </template> <script> import { mapGetters, mapActions } from "vuex"; export default { name: "TodoClearCompleted", computed: { ...mapGetters(["showClearCompletedButton"]) }, methods: { ...mapActions(["clearCompleted"]) } }; </script>TodoCheckAll コンポーネントを作成
タスクを一括で完了 ・ 未完了に切り替えるための
チェックボックスを表示させていますfrontend/src/components/TodoCheckAll.vue<template> <b-col cols="6"> <b-form-checkbox :checked="completedAll" @change="checkAll" >Check All</b-form-checkbox > </b-col> </template> <script> import { mapGetters, mapActions } from "vuex"; export default { name: "TodoCheckAll", computed: { ...mapGetters(["completedAll"]) }, methods: { ...mapActions(["checkAll"]) } }; </script>ブラウザで確認
Rail のサーバを起動
terminalrails sVue のサーバを起動
terminalcd frontend yarn serve
localhost:8080 にアクセスしてこんな画面が表示されたら成功です
Heroku へデプロイしてみる
事前準備
Heroku のアカウントがない場合はこちらから作成してください
デプロイには heroku toolbelt が必要なのでこちらからインストールしてください
mac の場合は Homebrew でインストール可能です
terminalbrew install heroku
プロジェクトを commit
プロジェクト直下へ移動し commit を行なってください
terminalgit init git add . git commit -m "init"vue.config.js を作成
frontend ディレクトリの直下に vue.config.js ファイルを作成し
build ファイルの出力先をプロジェクト直下の public ディレクトリへ変更しますfrontend/vue.config.jsmodule.exports = { outputDir: "../public" };Vue を build
terminalyarn buildプロジェクト直下の public ディレクトリに
build されたファイルが作成されていることを確認してくださいterminalpublic/ ├── css │ ├── app.27d4506b.css │ └── chunk-vendors.19588e8d.css ├── favicon.ico ├── img │ └── logo.82b9c7a5.png ├── index.html └── js ├── app.6635e2d3.js ├── app.6635e2d3.js.map ├── chunk-vendors.4ad97586.js └── chunk-vendors.4ad97586.js.mapHeroku にログイン
terminalheroku login上のコマンドを実行するとブラウザに切替わるのでボタンを押してログインしてください
Heroku にアプリを作成
アプリ名を入力すると URL にアプリ名が反映されます
https://アプリ名.herokuapp.com/
省略すると Heroku 側で自動的に割り振られますterminalheroku create アプリ名Heroku のリポジトリへ push
terminalgit push heroku masterデータベースの migration と テストデータを追加
terminalheroku run rails db:migrate heroku run rails db:seedブラウザで確認
terminalheroku openおわりに
最後まで読んでいただきありがとうございました。
おかしな部分がありましたら、ご指摘お願いします。
- 投稿日:2019-05-27T10:20:35+09:00
VeeValidateを使用したコンポーネントのユニットテストをする方法
はじめに
VeeValidate
をVue Test Utils
でテストができるようにしましたので備忘録として投稿します。
VeeValidate
をVue Test Utils
でテストする方法
VeeValidate
をVue Test Utils
でテストするとき下記のようにxxx.spec.jp
を作ります。import Vuex from 'vuex' import VeeValidate, { Validator } from 'vee-validate' import flushPromises from 'flush-promises' import Form from '@/components/Form.vue' const localVue = createLocalVue() localVue.use(Vuex) localVue.use(VeeValidate) describe('Form', () => { it('The name field is required', async () => { const wrapper = shallowMount(EntryForm, { localVue }) wrapper.find('[name="name"]').setValue('') wrapper.vm.$validator.validateAll() await flushPromises() expect(wrapper.vm.errors.first('name')).to.equal('The name field is required.') }) })おわりに
VeeValidateはフォームに入力してからエラーメッセージが表示されるまでタイムラグがあります。
初めは非同期でやっていたためにエラーメッセージが表示される前にテストが終わってエラーになってしまいました。
async
,await flushPromises()
を追記することで解決しました。
- 投稿日:2019-05-27T10:04:16+09:00
【Vue Test Utils】複数の `it` を行うと`$validator` にデータがどんどん蓄積されてしまう問題を解決
はじめに
VeeValidateを使っているコンポーネントを対象に複数の
it
を使ってテストしています。
$validator
が前のit
でのデータを保有したまま次のit
を実行するため、データがどんどん蓄積されています。
そのせいで、2つ目以降のit
テストで期待した通りの結果が返ってこなくてFAIL
になってしまいます。wrapper.vm.$validator // これにデータが蓄積されてしまう解決方法
$validator
がリセットされれば良いので、xx.spec.js
の中にafterEach(() => { wrapper.vm.$validator.reset() wrapper.destroy() })を入れてあげます。
これでit
ごとに$validator
がリセットされるようになり、期待した通りの結果が返ってくるようになりました。おわりに
VeeValidateを使っているコンポーネントを対象にしたテストを行うときは$validatorの状態に気をつけようと思いました。
- 投稿日:2019-05-27T09:35:49+09:00
[vee-validate] 「Validating a non-existent field: "". Use "attach()" first.」エラー問題を解決
- 投稿日:2019-05-27T09:35:49+09:00
「[vee-validate] Validating a non-existent field: "". Use "attach()" first.」エラー問題を解決
- 投稿日:2019-05-27T01:42:39+09:00
画像処理100本ノックにJavaScriptで挑戦してみた 【画像処理100本ノックJS】
概要
画像処理100本ノックをJavaScriptで挑戦してみました。
「ブラウザ上で完結させたい」 & 「デモを共有できたら面白い」という動機ではじめました。
まだ100問完了していませんが、ここまで解いてみた所感を書きます。とりあえず、、、「100問は辛いです。」
画像処理100本ノックについて
画像処理が初めての人のための問題集をつくったりました。(完成!!) 研究室の後輩用に作ったものです。 自然言語処理100本ノックがあるのに、画像処理のがなかったので作ってみました。 画像処理の基本のアルゴリズム理解につながると思います。 https://qiita.com/yoyoyo_/items/2ef53f47f87dcf5d1e14この「画像処理100本ノック」にはPythonとC++のコードが解答例として用意されています。
デモの例 ※ → デモ はこちらから(Gasyori100KnockJS)
いくつかのデモの例を紹介します。
実装の紹介
※ GitHub にソースを置いてます。
canvasを使った画像の表示と操作
画像の読み込み
画像の表示ピクセル値の操作にはcanvasのAPIを利用しています。
任意の画像を読み込む際には次のように実装しています。
<canvas id="canvas"></canvas>// canvas関連のオブジェクト const canvas = document.getElementById("canvas") const ctx = canvas.getContext("2d") // 任意の画像読み込み let image = new Image() image.src = "path/to/image.png" // 読み込み完了時のイベント image.onload = () => { canvas.width = image.width canvas.height = image.height ctx.drawImage(image, 0, 0, canvas.width, canvas.height) // canvas描画後、画像の処理を実行 }ピクセル操作
getImageDataメソッドでImageDataオブジェクトを取得しています。
このオブジェクトにはr, g, b, a の順に画像情報が格納されています。
putImageDataを使って編集したImageDataオブジェクトをcanvasに描画しています。これを用いることにより大概の画像の処理を行うことができます。
(canvasを用いず、Imageオブジェクトの画像情報に対して直接参照する方法があればいいんですけど... )
let src = ctx.getImageData(0, 0, canvas.width, canvas.height) let dst = ctx.createImageData(canvas.width, canvas.height) for (let i = 0; i < src.data.length; i += 4) { dst.data[i] = src.data[i] // r dst.data[i + 1] = src.data[i + 1] // g dst.data[i + 2] = src.data[i + 2] // b dst.data[i + 3] = src.data[i + 3] // a (透過度) }例えば、グレースケール画像であれば次のような処理になります。
const grayscale = (r, g, b) => 0.2126 * r + 0.7152 * g + 0.0722 * b // 略 for (let i = 0; i < src.data.length; i += 4) { let gray = grayscale(src.data[i], src.data[i + 1], src.data[i + 2]) dst.data[i] = gray[0] dst.data[i + 1] = gray[1] dst.data[i + 2] = gray[2] dst.data[i + 3] = src.data[i + 3] } ctx.putImageData(dst, 0, 0)こんな風に表示されます。
参考 : 画像をグレースケールに変換する JavaScript + canvas 【画像処理】
ヒストグラムの表示
このデモでは、ヒストグラムの表示にChart.jsを使っています。
実装については次のように行なっています。
ヒストグラム描画
import Chart from "chart.js" export default class Histogram { /** * ヒストグラムを描画する * @param {Object} canvas * @param {Object} data */ static renderHistogram(canvas, data) { let labels = new Array(data.length).fill('') new Chart(canvas, { type: 'bar', data:{ labels, datasets: [ { label: '画素値', data, backgroundColor: "rgba(80,80,80,0.5)" } ], }, options: { title: { display: true, text: 'Histogram' }, scales: { yAxes: [{ ticks: { suggestedMin: 0, } }] }, animation: { duration: 0 } } }) } }import Histogram from 'path/to/Histogram' const grayscale = (r, g, b) => 0.2126 * r + 0.7152 * g + 0.0722 * b // 略 let pixelValues = new Array(255).fill(0) for (let i = 0; i < src.data.length; i += 4) { let gray = grayscale(src.data[i], src.data[i + 1], src.data[i + 2]) gary = Math.floor(gray) pixelValues[gray]++ } Histogram.renderHistogram(canvas, pixelValues)参考 : 画像のヒストグラムを表示する Char.js JavaScript canvas
他
フレームワークにVueを使っています。
またSPAにも挑戦しました。コンポーネントの制御が難しく、処理がバグっている箇所があると思います()
まとめ : JSで挑戦するメリット・デメリット
ブラウザ上で動かせるのがJSを使う最大のメリットだと思います。
加えて、チャート系のライブラリが豊富なので、matplotlibに比べ、グラフィカルな表現がしやすいのも良い点だと感じました。一方で、行列演算に関してはJSではnumjsやmath.jsといったものはありますが、
Numpyほど簡潔に行列の処理を書くことはできません。(※今回のデモではアフィン変換などの行列演算を多用する箇所で math.js を使いました。)
また、フーリエ変換のデモでは、実装に複素数を利用しますが、
Pythonは「j」が利用できるのに対し、JSの場合は実部と虚部に分けるような処理に実装する必要がありました。改めてPython、Numpyの偉大さには感謝したいと思います。
他
画像処理100本ノックJS
https://s-yoshiki.github.io/Gasyori100knockJS/#/
画像処理100本ノックJS - GitHub
https://github.com/s-yoshiki/Gasyori100knockJS
JavaScriptで画像処理100本ノックに挑戦してみた
https://tech-blog.s-yoshiki.com/2019/03/1094/