- 投稿日:2020-09-25T23:22:10+09:00
クロージャ―を簡単にまとめてみた(1話)
クロージャ―
→「関数と、その関数が宣言されたレキシカル環境の組み合わせ」らしい。わからん。。。。
★かみ砕いていく★
「関数と、その関数を産んだ(定義した)親が持っている変数を合わせたもの」★さらにかみ砕いていく★
例を使って説明する。「一郎function」と、自分を産んだ「太郎function」が持っている変数(age,「次郎function」)を合わせたもの
クロージャ―の利点
①オブジェクトのカプセル化
→上の例でいうと、外部からは「age変数」、「一郎function」にはアクセスできない。
②イベントハンドラ(イベントが発生したら起動)として登録しておくことで、コールバックとして使える。
- 投稿日:2020-09-25T21:36:41+09:00
addEventListenerをクラス名で使用する方法
はじめに
クラス名でaddEventListenerを作る際にエラーが出たので、解決策を投稿します。
エラーが出たソースコード
HTML<body> <button class="btn">ボタン</button> </body>JavaScriptconst btn = document.getElementsByClassName('btn'); btn.addEventListener('click', () => { alert('上手に押せました!') })エラー文TypeError: btn.addEventListener is not a functionボタンタグがidであればエラーは出ませんが、classなのでエラーが出てしまいます。
解決方法
JavaScriptconst btn = document.getElementsByClassName('btn'); for (i = 0; i < btn.length; i++){ btn[i].addEventListener('click', () => { alert('上手に押せました!') }) }このようにfor文で囲んであげると、エラーが出ずに動作してくれます。
- 投稿日:2020-09-25T20:30:56+09:00
[PlayCanvas]入力イベントの取得
概要
キーボードやマウス、スマホのタッチ操作などのイベントを処理する機能。
実装例
マウス
mouse.jsvar Mouse = pc.createScript('mouse'); Mouse.prototype.initialize = function() { this.app.mouse.on(pc.EVENT_MOUSEDOWN, this.mouseDown, this); }; Mouse.prototype.mouseDown = function(event) { console.log("X: " + event.x.toString() + "Y: " + event.y.toString()); }; Mouse.prototype.update = function(dt) { // ホールドはupdateで取れる if( this.app.mouse.isPressed(pc.MOUSEBUTTON_LEFT) ){ console.log("Pressing Left Mouse Button.") } };initializeでthis.app.mouseに実行したい処理とイベントのキーを渡す事で実装する。
もしくは、wasPressedをupdateで呼び出して押されたかをチェックする事で実装もできる。(離した瞬間はwasReleased)マウスボタンを押し続けている(ホールド)間に処理を実行したい場合はisPressedで状態を取れる。
Event 内容 pc.EVENT_MOUSEDOWN 押された時 pc.EVENT_MOUSEUP 離した時 pc.EVENT_MOUSEMOVE 移動した時 pc.EVENT_MOUSEWHEEL ホイールを動かした時 キーボード
keyboard.jsvar Keyboard = pc.createScript('keyboard'); Keyboard.prototype.initialize = function() { this.app.keyboard.on(pc.EVENT_KEYDOWN, this.onKeyDown, this); }; Keyboard.prototype.onKeyDown = function(event) { // event.keyにキーコードが入っている if( event.key === pc.KEY_A ){ console.log( "Press A" ); } };initializeでthis.app.keyboardに実行したい処理とイベントのキーを渡す事で実装する。
マウス同様、isPressedやwasPressedがあるので、ホールドの検知やupdateでの記述が可能。
Event 内容 pc.EVENT_KEYDOWN 押された時 pc.EVENT_KEYUP 離した時 タッチ(スマホなど)
touch.jsvar Touch = pc.createScript('touch'); Touch.prototype.initialize = function() { this.app.touch.on(pc.EVENT_TOUCHSTART, this.touchStart, this); }; Touch.prototype.touchStart = function(event) { // event.touchesに画面に触れた場所の配列が入ってくる if( event.touches.length > 0 ){ console.log("X: " + event.touches[0].x.toString() + "Y: " + event.touches[0].y.toString()); } };initializeでthis.app.touchに実行したい処理とイベントのキーを渡す事で実装する。
タッチイベントはマウスとキーボードと違い、update内で検知を行う関数はない。
Event 内容 pc.EVENT_TOUCHSTART 触れた時 pc.EVENT_TOUCHEND 離した時 pc.EVENT_TOUCHMOVE スワイプされた時 pc.EVENT_TOUCHCANCEL 何かしらの理由で、タッチをキャンセルされた時
- 投稿日:2020-09-25T20:28:05+09:00
ワンタイムパスワードをサーバー側とクライアント側で作るサンプル
ワンタイムパスワードのロジックを作ってみた
2020/09/25 現在では何かと銀行がクラックされまくっているが、いずれもワンタイムパスワードのような認証が入ってない。もちろんワンタイムパスワードも「そのワンタイムの間に」盗まれればアウトなんだけど、常時通信内容を奪うのは盗む方も大変だ。
ただ、ワンタイムパスワードの仕組みが、Google Authenticator などの外部の仕組みに依存するのは、別の意味でリスク(急に仕様が変わるなど)。
なので、自前でどこまでできるかを検証。
どのようにして動く?
以下の要素を組み合わせて、ダイジェスト(sha256)を生成し、ダイジェストから数値6桁を取り出してます。
- ユーザ毎に違う秘密鍵(クライアントとサーバーで同じ秘密鍵を持ちます)
- タイムスタンプ(今回のサンプルでは60秒で割った整数部を使います)
- ソルト(サーバー側で保持する長い文字列)
サンプルソース
端末側
onetime.jsasync function digestMessage(message) { const encoder = new TextEncoder(); const data = encoder.encode(message); const hash = crypto.subtle.digest('SHA-256', data); return hash; } function buf2hex(buffer) { // buffer is an ArrayBuffer return Array.prototype.map.call(new Uint8Array(buffer), x => ('00' + x.toString(16)).slice(-2)).join(''); } async function onetimePass(secret_key){ const ts_min = parseInt( Date.now() / 60000 ).toString(); const buffer = await digestMessage(secret_key + '_and_sault' + ts_min); const hexString = '0x' + await buf2hex(buffer) ; const digitString = BigInt(hexString).toString(10); return digitString.slice(-6); }onetime.html<html> <script type="text/javascript" src="./onetime.js"></script> <body> <script> (async () => { document.write(await onetimePass('secret_key')); })(); </script> </body> </html>サーバー側
onetime.rbrequire 'digest/sha1' def onetime_pass(secret_key, next_min = false) # タイムスタンプを秒から分に丸める ts_min = Time.now.to_i / 60 ts_min += 1 if next_min # 次の分までカバーしたい場合(チェックを受ける側) text = "#{secret_key}_and_sault" Digest::SHA256.hexdigest("#{text}#{ts_min}").to_i(16).to_s[-6, 6] end # ちょうど一分をまたがることを想定して、一分後のパスワードも取得できるようにしてる puts onetime_pass('secret_key') puts onetime_pass('secret_key', true)実行結果
フロント側とサーバー側で同じ6桁ができてることがわかる。
(サーバー側のニ行目は、1分後のパスワード)心残り部分
- 久しぶりにJS書いたら何実行してもPromiseばかり帰ってくる。試行錯誤で, async, await を書きまくったけど、あんまり自身がない。
- RFCにもちゃんとワンタイムパスワードのロジックはあるらしいが読まずに「きっとこういうことだろう」で済ませてる
- 端末側はJSで書いちゃったけど、肝心の「秘密鍵を渡す方法」がこの記事では触れられてない。QRコードで渡すのが良さそうだけど、QRコードを読んで端末側のローカストレージに秘密鍵を保存するというプログラムの方がよほど上記のソースよりも長くなりそう。そういうことまで考えると、 Google Authenticator を使い、 Google Authenticator のサーバー側のライブラリを使っちゃうので解決か。
- sha256でダイジェストを作ったけど、10進数にして最後の6桁を取り出してる時点で、衝突確率は増える。
- 投稿日:2020-09-25T18:17:43+09:00
vue.js(nuxt.js)のdataはvuexのgettersを使ってデータ管理しよう
はじめに
vueinstanceでは親子component同士でdataを通信をする場合は
Props/$emitco
やcomponent同士でデータ通信をするためにはEvent Bus
を使うのが一般ですがこのように複雑なdata通信を簡単に通信することができる考え方がVuxです。
Vuexとは
ざっくりですが下記のイメージ図のように中央管理所からデータを必要な時呼び出しして使えるイメージです。
自分のイメージだと親と子component同士関係なく中央管理所からdataを管理し必要な時使うのがVuexでつまり中央管理所はVuexとのことです。
公式からの説明は下記です。
Vuex は Vue.js アプリケーションのための 状態管理パターン + ライブラリです。 これは予測可能な方法によってのみ状態の変異を行うというルールを保証し、アプリケーション内の全てのコンポーネントのための集中型のストアとして機能します。 また Vue 公式の開発ツール拡張と連携し、設定なしでタイムトラベルデバッグやステートのスナップショットのエクスポートやインポートのような高度な機能を提供します。
https://vuex.vuejs.org/ja/vuex使い方
nuxtインストールは公式サイト参考 https://ja.nuxtjs.org/guide/installation
nuxtをインストールしたらvuex
をインストールします。$ npm i vuexnuxtのプロジェクトを作成すると直下に
store
フォルダが作成されるのでstore
直下にindex.jsを新しく作って下記のサンプルコードのようにvuexをimportして必要なdataの登録をします。store/index.jsimport Vuex from 'vuex' export const appStore = () => { return new Vuex.Store({ state: {//data myData:{ firstName: 'thunder', lastName: 'fury', } }, mutations: { }, actions: { } }) }; export default appStorevuexの
state
はvueinstanceで言うと下記のdataの役割です。
storeにあるdataを使うためには$store
で呼び出しができます。<template lang="pug"> div p {{ $store.state.myData.firstName }} p {{ $store.state.myData.lastName }} </template>
これでも良いですがdataのオブジェクトの下層が深い場合など繰り返しコードが多くなってしまうのでその時使うのがGetters
です。Getters実行イメージ
storeのindex.jsにstateのdataをgettersに関数として登録してreturnして使える流れです。
store/index.js.jsgetters: { myData: state => { return state.myData }, //上下同じ getters: { myData: function(state) { return state.myData },どちらでも書き方は問題ないです。
少しでもにコード量を減らすためにはarrow functionが良いか思います。登録が終わったら使いたいvueのcomponent
mapGetters
をimportをしてcomputed
にstore/index.js
宣言した関数を配列の中に入れます。
そうすることに寄って$store.state
から呼び出したのをcomponent内でで宣言したようにで呼び出しすることができます。
<template lang="pug"> div p {{ myData.firstName }} // p {{ $store.state.myData.firstName }}が省略できる p {{ myData.lastName }} // p {{ $store.state.myData.firstName }}が省略できる </template> <script> import { mapGetters } from 'vuex' //ここにmapGettersをimport export default { computed: { ...mapGetters(['myData']) //ここでmapGettersを使う } } </script>
結果$store.state.myData.firstName
を呼び出した時と同じくなります。
getters
で宣言した関数名を別の名前で受け取りする方法も有って配列で受けたのをオブジェクトで受け取ってのkeyとvalueで割当して使うこともできます。computed: { ...mapGetters({ testData:'myData' }) }同じ結果です。
このように別のvueComponentを作ってもわざわざ処理をしなくてもimportだけでdataを呼び出しして使うことができます。グロバル関数系のmutations
やactions
もありますが自分はMixin
でグロバル化して使っても十分使えるかと思いました。終わり
ページを遷移した場合データの渡すしや受けるなどサイトが膨大になると複雑になるので中大サイトをもし、vueで作成する場合vuexのgettersを使って簡単に管理するのがどうですか
- 投稿日:2020-09-25T18:17:43+09:00
dataはvuexのgettersでデータ管理しよう
はじめに
vueinstanceでは親子component同士でdataを通信をする場合は
Props/$emitco
やcomponent同士でデータ通信をするためにはEvent Bus
を使うのが一般ですがこのように複雑なdata通信を簡単に通信することができる考え方がVuxです。
Vuexとは
ざっくりですが下記のイメージ図のように中央管理所からデータを必要な時呼び出しして使えるイメージです。
自分のイメージだと親と子component同士関係なく中央管理所からdataを管理し必要な時使うのがVuexでつまり中央管理所はVuexとのことです。
公式からの説明は下記です。
Vuex は Vue.js アプリケーションのための 状態管理パターン + ライブラリです。 これは予測可能な方法によってのみ状態の変異を行うというルールを保証し、アプリケーション内の全てのコンポーネントのための集中型のストアとして機能します。 また Vue 公式の開発ツール拡張と連携し、設定なしでタイムトラベルデバッグやステートのスナップショットのエクスポートやインポートのような高度な機能を提供します。
https://vuex.vuejs.org/ja/vuex使い方
nuxtインストールは公式サイト参考 https://ja.nuxtjs.org/guide/installation
nuxtをインストールしたらvuex
をインストールします。$ npm i vuexnuxtのプロジェクトを作成すると直下に
store
フォルダが作成されるのでstore
直下にindex.jsを新しく作って下記のサンプルコードのようにvuexをimportして必要なdataの登録をします。store/index.jsimport Vuex from 'vuex' export const appStore = () => { return new Vuex.Store({ state: {//data myData:{ firstName: 'thunder', lastName: 'fury', } }, mutations: { }, actions: { } }) }; export default appStorevuexの
state
はvueinstanceで言うと下記のdataの役割です。
storeにあるdataを使うためには$store
で呼び出しができます。<template lang="pug"> div p {{ $store.state.myData.firstName }} p {{ $store.state.myData.lastName }} </template>
これでも良いですがdataのオブジェクトの下層が深い場合など繰り返しコードが多くなってしまうのでその時使うのがGetters
です。Getters実行イメージ
storeのindex.jsにstateのdataをgettersに関数として登録してreturnして使える流れです。
store/index.js.jsgetters: { myData: state => { return state.myData }, //上下同じ getters: { myData: function(state) { return state.myData },どちらでも書き方は問題ないです。
少しでもにコード量を減らすためにはarrow functionが良いか思います。登録が終わったら使いたいvueのcomponent
mapGetters
をimportをしてcomputed
にstore/index.js
宣言した関数を配列の中に入れます。
そうすることに寄って$store.state
から呼び出したのをcomponent内でで宣言したようにで呼び出しすることができます。
<template lang="pug"> div p {{ myData.firstName }} // p {{ $store.state.myData.firstName }}が省略できる p {{ myData.lastName }} // p {{ $store.state.myData.firstName }}が省略できる </template> <script> import { mapGetters } from 'vuex' //ここにmapGettersをimport export default { computed: { ...mapGetters(['myData']) //ここでmapGettersを使う } } </script>
結果$store.state.myData.firstName
を呼び出した時と同じくなります。
getters
で宣言した関数名を別の名前で受け取りする方法も有って配列で受けたのをオブジェクトで受け取ってのkeyとvalueで割当して使うこともできます。computed: { ...mapGetters({ testData:'myData' }) }同じ結果です。
このように別のvueComponentを作ってもわざわざ処理をしなくてもimportだけでdataを呼び出しして使うことができます。グロバル関数系のmutations
やactions
もありますが自分はMixin
でグロバル化して使っても十分使えるかと思いました。終わり
ページを遷移した場合データの渡すしや受けるなどサイトが膨大になると複雑になるので中大サイトをもし、vueで作成する場合vuexのgettersを使って簡単に管理するのがどうですか
- 投稿日:2020-09-25T18:17:43+09:00
Nuxr.jsのdataはvuexのstate,gettersでデータ管理しようstate、
はじめに
vueinstanceでは親子component同士でdataを通信をする場合は
Props/$emitco
やcomponent同士でデータ通信をするためにはEvent Bus
を使うのが一般ですがこのように複雑なdata通信を簡単に通信することができる考え方がVuxです。
Vuexとは
ざっくりですが下記のイメージ図のように中央管理所からデータを必要な時呼び出しして使えるイメージです。
自分のイメージだと親と子component同士関係なく中央管理所からdataを管理し必要な時使うのがVuexでつまり中央管理所はVuexとのことです。
公式からの説明は下記です。
Vuex は Vue.js アプリケーションのための 状態管理パターン + ライブラリです。 これは予測可能な方法によってのみ状態の変異を行うというルールを保証し、アプリケーション内の全てのコンポーネントのための集中型のストアとして機能します。 また Vue 公式の開発ツール拡張と連携し、設定なしでタイムトラベルデバッグやステートのスナップショットのエクスポートやインポートのような高度な機能を提供します。
https://vuex.vuejs.org/ja/vuex使い方
nuxtインストールは公式サイト参考 https://ja.nuxtjs.org/guide/installation
nuxtをインストールしたらvuex
をインストールします。$ npm i vuexnuxtのプロジェクトを作成すると直下に
store
フォルダが作成されるのでstore
直下にindex.jsを新しく作って下記のサンプルコードのようにvuexをimportして必要なdataの登録をします。store/index.jsimport Vuex from 'vuex' export const appStore = () => { return new Vuex.Store({ state: {//data myData:{ firstName: 'thunder', lastName: 'fury', } }, mutations: { }, actions: { } }) }; export default appStorevuexの
state
はvueinstanceで言うと下記のdataの役割です。
storeにあるdataを使うためには$store
で呼び出しができます。<template lang="pug"> div p {{ $store.state.myData.firstName }} p {{ $store.state.myData.lastName }} </template>
これでも良いですがdataのオブジェクトの下層が深い場合など繰り返しコードが多くなってしまうのでその時使うのがGetters
です。Getters実行イメージ
storeのindex.jsにstateのdataをgettersに関数として登録してreturnして使える流れです。
store/index.js.jsgetters: { myData: state => { return state.myData }, //上下同じ getters: { myData: function(state) { return state.myData },どちらでも書き方は問題ないです。
少しでもにコード量を減らすためにはarrow functionが良いか思います。登録が終わったら使いたいvueのcomponent
mapGetters
をimportをしてcomputed
にstore/index.js
宣言した関数を配列の中に入れます。
そうすることに寄って$store.state
から呼び出したのをcomponent内でで宣言したようにで呼び出しすることができます。
<template lang="pug"> div p {{ myData.firstName }} // p {{ $store.state.myData.firstName }}が省略できる p {{ myData.lastName }} // p {{ $store.state.myData.firstName }}が省略できる </template> <script> import { mapGetters } from 'vuex' //ここにmapGettersをimport export default { computed: { ...mapGetters(['myData']) //ここでmapGettersを使う } } </script>
結果$store.state.myData.firstName
を呼び出した時と同じくなります。
getters
で宣言した関数名を別の名前で受け取りする方法も有って配列で受けたのをオブジェクトで受け取ってのkeyとvalueで割当して使うこともできます。computed: { ...mapGetters({ testData:'myData' }) }同じ結果です。
このように別のvueComponentを作ってもわざわざ処理をしなくてもimportだけでdataを呼び出しして使うことができます。グロバル関数系のmutations
やactions
もありますが自分はMixin
でグロバル化して使っても十分使えるかと思いました。終わり
ページを遷移した場合データの渡すしや受けるなどサイトが膨大になると複雑になるので中大サイトをもし、vueで作成する場合vuexのgettersを使って簡単に管理するのがどうですか
- 投稿日:2020-09-25T18:17:43+09:00
Nuxt.jsのdataはvuexのstate,gettersでデータ管理しよう
はじめに
vueinstanceでは親子component同士でdataを通信をする場合は
Props/$emitco
やcomponent同士でデータ通信をするためにはEvent Bus
を使うのが一般ですがこのように複雑なdata通信を簡単に通信することができる考え方がVuxです。
Vuexとは
ざっくりですが下記のイメージ図のように中央管理所からデータを必要な時呼び出しして使えるイメージです。
自分のイメージだと親と子component同士関係なく中央管理所からdataを管理し必要な時使うのがVuexでつまり中央管理所はVuexとのことです。
公式からの説明は下記です。
Vuex は Vue.js アプリケーションのための 状態管理パターン + ライブラリです。 これは予測可能な方法によってのみ状態の変異を行うというルールを保証し、アプリケーション内の全てのコンポーネントのための集中型のストアとして機能します。 また Vue 公式の開発ツール拡張と連携し、設定なしでタイムトラベルデバッグやステートのスナップショットのエクスポートやインポートのような高度な機能を提供します。
https://vuex.vuejs.org/ja/vuex使い方
nuxtインストールは公式サイト参考 https://ja.nuxtjs.org/guide/installation
nuxtをインストールしたらvuex
をインストールします。$ npm i vuexnuxtのプロジェクトを作成すると直下に
store
フォルダが作成されるのでstore
直下にindex.jsを新しく作って下記のサンプルコードのようにvuexをimportして必要なdataの登録をします。store/index.jsimport Vuex from 'vuex' export const appStore = () => { return new Vuex.Store({ state: {//data myData:{ firstName: 'thunder', lastName: 'fury', } }, mutations: { }, actions: { } }) }; export default appStorevuexの
state
はvueinstanceで言うと下記のdataの役割です。
storeにあるdataを使うためには$store
で呼び出しができます。<template lang="pug"> div p {{ $store.state.myData.firstName }} p {{ $store.state.myData.lastName }} </template>
これでも良いですがdataのオブジェクトの下層が深い場合など繰り返しコードが多くなってしまうのでその時使うのがGetters
です。Getters実行イメージ
storeのindex.jsにstateのdataをgettersに関数として登録してreturnして使える流れです。
store/index.js.jsgetters: { myData: state => { return state.myData }, //上下同じ getters: { myData: function(state) { return state.myData },どちらでも書き方は問題ないです。
少しでもにコード量を減らすためにはarrow functionが良いか思います。登録が終わったら使いたいvueのcomponent
mapGetters
をimportをしてcomputed
にstore/index.js
宣言した関数を配列の中に入れます。
そうすることに寄って$store.state
から呼び出したのをcomponent内でで宣言したようにで呼び出しすることができます。
<template lang="pug"> div p {{ myData.firstName }} // p {{ $store.state.myData.firstName }}が省略できる p {{ myData.lastName }} // p {{ $store.state.myData.firstName }}が省略できる </template> <script> import { mapGetters } from 'vuex' //ここにmapGettersをimport export default { computed: { ...mapGetters(['myData']) //ここでmapGettersを使う } } </script>
結果$store.state.myData.firstName
を呼び出した時と同じくなります。
getters
で宣言した関数名を別の名前で受け取りする方法も有って配列で受けたのをオブジェクトで受け取ってのkeyとvalueで割当して使うこともできます。computed: { ...mapGetters({ testData:'myData' }) }同じ結果です。
このように別のvueComponentを作ってもわざわざ処理をしなくてもimportだけでdataを呼び出しして使うことができます。グロバル関数系のmutations
やactions
もありますが自分はMixin
でグロバル化して使っても十分使えるかと思いました。終わり
ページを遷移した場合データの渡すしや受けるなどサイトが膨大になると複雑になるので中大サイトをもし、vueで作成する場合vuexのgettersを使って簡単に管理するのがどうですか
- 投稿日:2020-09-25T17:17:21+09:00
チェックボックス全選択した時のみ適用する確認ボタン
<script type="text/javascript"> function change() { var element; if ((document.getElementById("check1").checked) && (document.getElementById("check2").checked) && (document.getElementById("check3").checked)) { element = document.getElementById("check"); element.disabled = false; } else { element = document.getElementById("check"); element.disabled = true; } } </script>チェック出来ているかの確認
<div> <button class="input_btn_dou f_btn dark" type="button" value="" onclick="history.go(-1); return false;" > 戻る <span class="icon-arow-left01 single_left" /> </button> <button class="input_btn_dou_red f_btn red c-terms__submit changed" type="submit" id="check" value="" disabled> 解約する <span class="icon-arow-right01 single_right"></span> </button> </form>div id="wrapper check"> <p class="box" form="check"> <input type="checkbox" name="check" id="check1" onchange="change()"> <label class="checkbox_font" for="check1"> 本当に良いですか?1 </label> </p> <p class="box" form="check"> <input type="checkbox" name="check" id="check2" onchange="change()"> <label class="checkbox_font" for="check2"> 本当に良いですか?2 </label> </p> <p class="box" form="check"> <input type="checkbox" name="check" id="check3" onchange="change()"> <label class="checkbox_font" for="check3"> 本当に良いですか?3 </label> </p> </div><button class="input_btn_dou f_btn dark" type="button" value="" onclick="history.go(-1); return false;"> 戻る <span class="icon-arow-left01 single_left"></span> </button> <button class="input_btn_dou_red not-btn red c-terms__submit" type="submit" id="check-btn1" value="" disabled> 解約する <span class="icon-arow-right01 single_right"></span> </button> <button class="input_btn_dou_red f_btn red c-terms__submit display_btn " type="submit" id="check-btn2" value="" disabled> 解約する <span class="icon-arow-right01 single_right"></span> </button> </form> <script type="text/javascript"> function change() { if ((document.getElementById("check1").checked) && (document.getElementById("check2").checked) && (document.getElementById("check3").checked)) { document.getElementById("check-btn2").disabled = false; document.getElementById("check-btn1").style.display = "none"; document.getElementById("check-btn2").style.display = "inline"; } else { document.getElementById("check-btn1").disabled = true; document.getElementById("check-btn1").style.display = "inline"; document.getElementById("check-btn2").style.display = "none"; } } </script>
- 投稿日:2020-09-25T17:17:21+09:00
チェックボックス全選択した時のみ適用出来るボタン
<!DOCTYPE html> <head> </head> <body> <p>確認ボタン</p> <p form="check"> <input type="checkbox" onchange="change()" id="check1"> <label class="checkbox_font" for="check1"> 確認チェックボックス1 </label> </p> <p form="check"> <input type="checkbox" onchange="change()" id="check2"> <label class="checkbox_font" for="check2"> 確認チェックボックス2 </label> </p> <p form="check"> <input type="checkbox" onchange="change()" id="check3"> <label class="checkbox_font" for="check3"> 確認チェックボックス3 </label> </p> <button type="submit" id="check-btn" disabled> 承認 </button> <script type="text/javascript"> function change() { if ((document.getElementById("check1").checked) && (document.getElementById("check2").checked) && (document.getElementById("check3").checked)) { document.getElementById("check-btn").disabled = false; } else { document.getElementById("check-btn").disabled = true; } } </script> </body> </html>全てのチェックボックスをチェックする事によってボタンを押すことが出来る
- 投稿日:2020-09-25T17:17:21+09:00
複数チェックボックスを全選択した時のみ押せるボタン
<!DOCTYPE html> <head> </head> <body> <p>確認ボタン</p> <p form="check"> <input type="checkbox" onchange="change()" id="check1"> <label for="check1"> 確認チェックボックス1 </label> </p> <p form="check"> <input type="checkbox" onchange="change()" id="check2"> <label for="check2"> 確認チェックボックス2 </label> </p> <p form="check"> <input type="checkbox" onchange="change()" id="check3"> <label for="check3"> 確認チェックボックス3 </label> </p> <button type="submit" id="check-btn" disabled> 承認 </button> <script type="text/javascript"> function change() { if ((document.getElementById("check1").checked) && (document.getElementById("check2").checked) && (document.getElementById("check3").checked)) { document.getElementById("check-btn").disabled = false; } else { document.getElementById("check-btn").disabled = true; } } </script> </body> </html>全てのチェックボックスをチェックする事によってボタンを押すことが出来る
- 投稿日:2020-09-25T15:03:43+09:00
Vue.jsでビジュアルコンテンツエディタ作成
概要
記事コンテンツの管理ツールの需要があって、Vue.jsを用いて作成てみました。
諸事情によってサーバーサイドを利用できず、すべてフロントで完結する必要があります。
イメージとしては簡易版のワードプレス管理画面のようなものを想定して設計しました。
- フロントエンド(エンドユーザー向け)
- ReactやVueなどで記事コンテンツのjsonを用いて描画する
- バックエンド
- エディタを使って、このような記事コンテンツのjsonファイルを作成、管理、更新
第一弾としてはバックエンドのパートをご紹介します。
デモはこちらページ構成
- トップ
- 既存記事コンテンツのjsonをアップロードして、リスト形式で表示、完成したデータをjson形式でダウンロード
- 記事作成画面
- Vue-Quill-Editorを使ってビジュアルエディタを導入、各項目の入力
- 記事編集画面
- 基本的には作成と同じく、各項目の入力、既存記事コンテンツの更新、削除
必要なライブラリー
vue:ページ遷移時に、propsの受け渡しが必要ですので、CLIの導入が必要です。
vue-router:ページ遷移のために、必要となります。
axios:画像ファイルのアップロード時に必要です。
vue-quill-editor:ビジュアルエディタのプラグインです。ファイルの構成
main.js
router.jsの導入
main.jsimport Vue from 'vue' import App from './App.vue' import router from './router' Vue.config.productionTip = false new Vue({ router, render: h => h(App) }).$mount('#app')router/index.js
各ページへのURLルールとテンプレートを設定
データの受け渡しが必要ですので、propsはtrueにします。router/index.jsimport Vue from 'vue' import VueRouter from 'vue-router' import Home from '../views/Home.vue' Vue.use(VueRouter) const routes = [ { path: '/', name: 'Home', component: Home, props: true }, { path: '/new', name: 'New', component: () => import('../views/New.vue'), props: true }, { path: '/detail/:aid', name: 'Detail', component: () => import('../views/Detail.vue'), props: true } ] const router = new VueRouter({ mode: 'history', base: process.env.BASE_URL, routes }) export default routerApp.vue
特筆すべき点もなく、基本的に各Viewsに振り分けます。
App.vue<template> <div id="app"> <router-view/> </div> </template>Home.vue
既存記事コンテンツのjsonをアップロードして、リスト形式で表示、完成したデータをjson形式でダウンロード、ほかのページへ遷移するときに、記事コンテンツの最新情報を渡す
views/Home.vue<template> <div> <header> <p><img src="logo.png" alt="" width="48"></p> <h1>Quill Editor</h1> </header> <main> <div class="homeArea"> <div class="homeDisplay"> <p>Please upload a json file.</p> <input class="jsonUL" type="file" @change="upload" /> <h2>Article List</h2> <ul class="articleList"> <li v-for="item in items" :key="item.aid"> <router-link v-bind:to="{ name: 'Detail', params: { aid:item.aid, getItems: items }}"> <div> <h3>{{ item.title }}</h3> <p>{{ item.description }}</p> </div> <div> <img :src="item.thumbnail"> </div> </router-link> </li> </ul> </div> <div class="homeDescription"> <h3>How to use</h3> <ul> <li>Upload a json format data file (<span class="sample"><a v-on:click="sample()">Sample</a></span>) to start.</li> <li>Click "Create a new article" to create a new one, input the necessary information and click "Add" to finish.</li> <li>Click each article to edit contents, and click "save" when finished.</li> <li>Click "Download" to export the newest article data.</li> <li>Please do not use browser "back" or "refresh", this may remove all the data.</li> </ul> <h3>Element in article</h3> <dl> <dt>aid (Require/Unique)</dt> <dd>Article id, can not change after created, please do not use same aid for different article, we recommond "yyyymmddID" (eg. "2020101401")</dd> <dt>Title (Require)</dt> <dd>Article title, can be seen in list</dd> <dt>Description (Require)</dt> <dd>Article description, can be seen in list</dd> <dt>Thumbnail (Require)</dt> <dd>Article thumbnail image, can be seen in list</dd> <dt>Content (Require)</dt> <dd>Article contents</dd> <dt>Category</dt> <dd>Article category, planning to be used for filter</dd> <dt>Tag</dt> <dd>Article category, planning to be used for filter</dd> </dl> </div> </div> </main> <footer> <router-link class="greenBtn button" v-bind:to="{ name: 'New', params: { getItems: items }}">Create a new article</router-link> <a class="blackBtn button" v-on:click="download()">Download</a> </footer> </div> </template> <script> import Vue from 'vue' import VueQuillEditor from 'vue-quill-editor' import 'quill/dist/quill.core.css' import 'quill/dist/quill.snow.css' import 'quill/dist/quill.bubble.css' Vue.use(VueQuillEditor) const today = new Date(); const update = today.getFullYear() + "/" + (today.getMonth() + 1) + "/" + today.getDate() + " " + today.getHours() + ":" + today.getMinutes() export default { props: { getItems: Array, }, data(){ return { results: [], items: this.getItems, dlData: { update: update, data: this.getItems }, sampleData: { update: update, data: [ { aid: '2020010101', title: 'Sample Title1', description: 'Sample Description1', thumbnail: '', category: 'Sample Category1', tag: 'Sample Tag1', content: 'Sample Contents1' }, { aid: '2020010203', title: 'Sample Title2', description: 'Sample Description2', thumbnail: '', category: 'Sample Category2', tag: 'Sample Tag2', content: 'Sample Contents2' }, ] } } }, methods: { upload: function(e) { const file = e.target.files[0] const reader = new FileReader() reader.onload = (e) => { this.results = JSON.parse(e.target.result) this.items = this.results.data } reader.readAsText(file); }, download: function() { const output = this.dlData const data = JSON.stringify(output) const blob = new Blob([data], { type: 'text/plain' }) const e = document.createEvent('MouseEvents'), a = document.createElement('a'); a.download = "data.json"; a.href = window.URL.createObjectURL(blob); a.dataset.downloadurl = ['text/json', a.download, a.href].join(':'); e.initEvent('click', true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null); a.dispatchEvent(e); }, sample: function() { const output = this.sampleData const data = JSON.stringify(output) const blob = new Blob([data], { type: 'text/plain' }) const e = document.createEvent('MouseEvents'), a = document.createElement('a'); a.download = "data.json"; a.href = window.URL.createObjectURL(blob); a.dataset.downloadurl = ['text/json', a.download, a.href].join(':'); e.initEvent('click', true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null); a.dispatchEvent(e); } } } </script>Detail.vue
Vue-Quill-Editorを使ってビジュアルエディタを導入、各項目の入力、保存、トップへ戻すときにデータを渡す
views/Detail.vue<template> <div> <header> <p><img src="logo.png" alt="" width="48"></p> <h1>Article Detail</h1> </header> <main> <div class="detail"> <div id="editorArea"> <h2>Editor</h2> <div class="inner"> <h3 class="required">Contents</h3> <quill-editor v-model="content" ref="quillEditor" :options="editorOption"></quill-editor> </div> <h3>Information</h3> <dl class="detailList"> <dt>aid</dt> <dd>{{aid}}</dd> <dt class="required">Title</dt> <dd><input v-model="title" size="40"></dd> <dt class="required description">Description</dt> <dd class="description"><textarea v-model="description" rows="3" cols="40"></textarea></dd> <dt>Category</dt> <dd><input v-model="category" size="15"></dd> <dt>Tag</dt> <dd><input v-model="tag" size="15"></dd> <dt class="required">Thumbnail</dt> <dd><input type="file" @change="upload" accept="image/*" /></dd> </dl> <p><img class="thumbnail" :src="thumbnail"></p> </div> <div id="previewArea"> <h2>Preview</h2> <div class="inner"> <div id="preview" class="content ql-editor" v-html="content"></div> </div> </div> </div> </main> <footer> <a class="greenBtn button" v-on:click="save()">Save</a> <router-link class="redBtn button" @click.native="remove()" v-bind:to="{ name: 'Home', params: { getItems: postItems }}">Remove</router-link> <router-link class="blackBtn button" v-bind:to="{ name: 'Home', params: { getItems: postItems }}">Back to home</router-link> </footer> </div> </template> <script> import Vue from 'vue' import axios from 'axios'; import VueQuillEditor from 'vue-quill-editor' import 'quill/dist/quill.core.css' import 'quill/dist/quill.snow.css' import 'quill/dist/quill.bubble.css' Vue.use(VueQuillEditor) export default { props: { aid: String, getItems: Array, }, data() { return { item : this.getItems.filter(item => item.aid === this.aid)[0], index: this.getItems.findIndex(item => item.aid === this.aid), content: this.getItems.filter(item => item.aid === this.aid)[0].content, title: this.getItems.filter(item => item.aid === this.aid)[0].title, description: this.getItems.filter(item => item.aid === this.aid)[0].description, thumbnail: this.getItems.filter(item => item.aid === this.aid)[0].thumbnail, category: this.getItems.filter(item => item.aid === this.aid)[0].category, tag: this.getItems.filter(item => item.aid === this.aid)[0].tag, postItems: this.getItems, editorOption: { theme: 'snow' } } }, methods: { upload(e) { const file = e.target.files[0] const reader = new FileReader() reader.onload = (e) => { this.thumbnail = e.target.result this.postData() } reader.readAsDataURL(file) }, postData() { const params = new FormData() params.append('image', this.image) axios.post('http://0.0.0.0:9999/', params).then(res => { this.thumbnail = res.data.url }) }, save: function() { this.postItems[this.index].content = this.content this.postItems[this.index].title = this.title this.postItems[this.index].description = this.description this.postItems[this.index].thumbnail = this.thumbnail this.postItems[this.index].category = this.category this.postItems[this.index].tag = this.tag }, remove: function() { this.postItems.splice(this.index, 1); } } } </script>New.vue
Vue-Quill-Editorを使ってビジュアルエディタを導入、各項目の入力、保存、トップへ戻すときにデータを渡す
views/New.vue<template> <div> <header> <p><img src="logo.png" alt="" width="48"></p> <h1>New Article</h1> </header> <main> <div class="detail"> <div id="editorArea"> <h2>Editor</h2> <div class="inner"> <h3 class="required">Contents</h3> <quill-editor v-model="content" ref="quillEditor" :options="editorOption"></quill-editor> </div> <h3>Information</h3> <dl class="detailList"> <dt class="required">aid</dt> <dd><input v-model="aid" size="15"></dd> <dt class="required">Title</dt> <dd><input v-model="title" size="40"></dd> <dt class="required description">Description</dt> <dd class="description"><textarea v-model="description" rows="3" cols="40"></textarea></dd> <dt>Category</dt> <dd><input v-model="category" size="15"></dd> <dt>Tag</dt> <dd><input v-model="tag" size="15"></dd> <dt class="required">Thumbnail</dt> <dd><input type="file" @change="upload" accept="image/*" /></dd> </dl> <p><img class="thumbnail" :src="thumbnail"></p> </div> <div id="previewArea"> <h2>Preview</h2> <div class="inner"> <div id="preview" class="content ql-editor" v-html="content"></div> </div> </div> </div> </main> <footer> <router-link class="greenBtn button" @click.native="add()" v-bind:to="{ name: 'Home', params: { getItems: postItems }}">Add</router-link> <router-link class="blackBtn button" v-bind:to="{ name: 'Home', params: { getItems: postItems }}">Back to home</router-link> </footer> </div> </template> <script> import Vue from 'vue' import axios from 'axios'; import VueQuillEditor from 'vue-quill-editor' import 'quill/dist/quill.core.css' import 'quill/dist/quill.snow.css' import 'quill/dist/quill.bubble.css' Vue.use(VueQuillEditor) export default { props: { getItems: Array, }, data() { return { aid: '', content: '', title: '', description: '', thumbnail: '', category: '', tag: '', postItems: this.getItems, editorOption: { theme: 'snow' }, } }, methods: { upload(e) { const file = e.target.files[0] const reader = new FileReader() reader.onload = (e) => { this.thumbnail = e.target.result this.postData() } reader.readAsDataURL(file) }, postData() { const params = new FormData() params.append('image', this.image) axios.post('http://0.0.0.0:9999/', params).then(res => { this.thumbnail = res.data.url }) }, add: function() { const newItem = { "aid": this.aid, "title": this.title, "description": this.description, "thumbnail": this.thumbnail, "category": this.category, "tag": this.tag, "content": this.content } this.postItems.push(newItem) } } } </script>引き続き、第二弾ではフロント側の描画をご紹介致します。
完成次第、またこちらにて投稿します。
- 投稿日:2020-09-25T13:55:42+09:00
【JS学習その⑦】JavaScriptにおけるthis
JS学習シリーズの目的
このシリーズは、私ジャックが学んだJavaScriptのメカニズムについてアウトプットも兼ねて、
皆さんと知識や理解を共有するためのものです。
(理解に間違いがあればご指摘いただけると幸いです)thisとは
「呼び出し元のオブジェクトへの参照を保持するキーワード」
main.jsconst person = { name: 'Tom', hello: function() { console.log('Hello ' + this.name); } } person.hello(); /*Hello Tom*/上記のコードでは、
1.JavaScriptではまず変数personを呼び出す
2.person内のhelloメソッドに参照が向く
3.helloメソッドが参照しているfunctionを実行する↑の流れになっています。
thisはこの時、呼び出し元のオブジェクトであるpersonを参照するので、this.nameはpersonオブジェクトのnameプロパティを参照するというわけです。参照のコピーとthis
thisの基礎が分かったところで、次のようなコードを見てみましょう
main.jswindow.name = 'John'; const person = { name: 'Tom', hello: function() { console.log('Hello ' + this.name); } } const ref = person.hello; ref(); /*Hello John*/ person.hello(); /*Hello Tom*/変数refにperson.helloを代入した場合、変数person内にあるhelloメソッドへの参照をコピーします。
この状態でref()を実行すると、変数personを呼び出していないので、thisは呼び出し元のオブジェクトがpersonではなくなります。
この時、thisがどのオブジェクトを参照するのかというと、グローバルオブジェクト(windowオブジェクト)を参照します。ここで非常に重要なことを言いますが、
オブジェクトのメソッドとして実行される場合
'this' => 呼び出し元のオブジェクト関数として実行される場合
'this' => グローバルオブジェクト↑のようにthisの参照先は変わります。
したがって
main.jswindow.name = John; function a() { console.log('Hello ' + this.name); } a(); /*Hello John*/上記のようなコードでは、関数a内のthisはグローバルオブジェクトを参照するので、a()を実行した結果は、'Hello John'となります。
コールバック関数とthis
オブジェクトのメソッドをコールバック関数として実行した場合を見てみます
main.jswindow.name = 'John'; const person = { name: 'Tom', hello: function() { console.log('Hello ' + this.name); } } person.hello(); /*Hello Tom*/ function fn(ref) { ref(); } fn(person.hello); /*Hello John*/上記のコードのように、
person.hello()を実行した場合は、今までどおりthisはpersonを呼び出し元のオブジェクトとして参照します。
しかし、
関数fnにコールバック関数としてperson.helloを渡した場合、personオブジェクト内のhelloの参照先であるfunctionを変数(引数)に代入しているのと同じことなので、
コールバック関数ref()はperson.helloを参照先とする関数として実行され、thisはグローバルオブジェクトを参照します。bindとthis
関数として実行するけど、thisの参照先を呼び出し元のオブジェクトにしたい。という時は、'bind'というメソッドを使います。
main.jswindow.name = 'John'; const person = { name: 'Tom', hello: function() { console.log('Hello ' + this.name); } } person.hello(); /*Hello Tom*/ const helloTom = person.hello.bind(person); function fn(ref) { ref(); } fn(helloTom); /*Hello Tom*/上記のコードのように、
変数helloTomにperson.hello.bind(person)と書くと、'bind'メソッドによってperson.helloのthisの参照先がpersonオブジェクトに固定されます。
したがって、コールバック関数としてfn(helloTom)を実行すると、'Hello Tom'と出力されます。このように、'bind'メソッドによるthisの参照先の固定をbindによるthisの束縛と表現します。
また、'bind'は'this'の参照先だけではなく、そのメソッドまたは関数の'引数'も固定できます。
main.jswindow.name = 'John'; function a(name) { console.log('hello ' + name); } const b = a.bind(null, 'Tim'); b(); /*hello Tim*/上記のように書いた場合、'bind'の第2引数以降に指定したキーワードで関数の引数を固定できます。(※引数の固定は、複数指定できます)
※'bind'の第1引数で'this'の参照先を固定しない場合は、'null'を指定します。ここで、重要なメカニズムとして'bind'メソッドでは、(今回の例で説明すると)
1.person.helloの参照先であるfunctionが存在する
2.person.hello.bind(person)によって'this'の参照先をpersonに固定したfunction'が別のメモリ空間にコピーされるこの、'this'の参照先を固定した別のfunction'がメモリ空間に作成されるというところは大事なのでしっかり理解しておきましょう!
call,applyと'this'
前述した'bind'のもう一つの特徴として'bind'の使用時点で実行はしないという点があります。
ここで紹介する'call'メソッドと'apply'メソッドは、使用時点で実行します。
main.jsfunction a(name, name1) { console.log('hello ' + this.name + '' + name + ' ' + name1); } const tim = {name: 'Tim'}; const b = a.bind(tim, 'Bob', 'John'); b(); /*hello Tim Bob John*/ a.apply(tim, ['Bob', 'John']); /*hello Tim Bob John*/ a.call(tim, 'Bob', 'John'); /*hello Tim Bob John*/上記のコードのように、
'bind'では使用時点では実行されず、b()のように関数を実行した時点で実行されますが、
'call','apply'では、使用時点で実行されます。
'call'と'apply'の違いは、固定する引数の指定をする際に、'call'は'bind'と同じように「,」(カンマ)区切りで指定しますが、'apply'は配列で指定します。アロー関数と'this'
今まで、'this'について解説してきました。ここでアロー関数と'this'について解説します。
アロー関数とは、「無名関数を記述しやすくした省略記法」です。
そして結論から言いますが、
「アロー関数内では、'this'という値を保持しません」
では、アロー関数内で'this'を使ったらどうなるか?次のコードを見てみましょう。main.jswindow.name = 'John'; const person = { name: 'Tom'; hello: () => { console.log('Hello ' + this.name); } } person.hello(); /*Hello John*/上記のコードでは、
person.helloメソッドではアロー関数を使用しています。そして、そのメソッド内で'this'という値を使用しています。
前述した通り、アロー関数では'this'という値を保持しません。そこで、'this'はスコープチェーンをたどってレキシカルスコープ(外部スコープ)から値を探します。
今回の場合、直近の値はグローバルオブジェクト(windowオブジェクト)内のwindow.nameになります。
よってperson.hello()の値は'Hello John'となります。まとめ
いかがでしたでしょうか。
JavaScriptにおいて'this'は初学者が躓きやすいところだと思いますが、しっかり理解しておきましょう!おまけ
今回の解説では、
オブジェクト内のメソッドを下記のように書きましたが、main.jsconst person = { name: 'Tom', hello: function() { console.log('Hello ' + this.name); } } person.hello(); /*Hello Tom*/下記のように省略した記法もあり、主にこちらの書き方で書かれます。この書き方にも慣れておきましょう。
main.jsconst person = { name: 'Tom', hello() { console.log('Hello ' + this.name); } } person.hello(); /*Hello Tom*/
- 投稿日:2020-09-25T13:42:39+09:00
Angular+Golang(gin)でSPAを作る
目標
Angularで作成したSPA(シングルページアプリケーション)に対して、GolangのWebフレームワークであるginを使ってHTTPアクセスできるようにします。
なぜginを使うか。
- ログがわかりやすいから。
- 記事が見当たらなかったから
前提
「Angularのチュートリアル」を通していること。
チュートリアル程度のAngular CLIが使えること。
既に、ご自身で作成されたAngularプロジェクトがあることを想定しています。
ご自身で作成されたAngularプロジェクトがない場合:
後述する「プロジェクトをコピー」の手順を無視して進めてください。
デフォルトで生成されるSPAを表示することができます。手順
プロジェクト作成
始めのプロジェクト構成は以下のようにしています。
go-angular └── main.go
go-angular
ディレクトリに移動して、Angularプロジェクトを作成します。UHNaKZ:go-angular $ ng new view ? Would you like to add Angular routing? Yes // Yesを入力 ? Which stylesheet format would you like to use? CSS // CSSを選択
プロジェクトの構成が以下のようになります。
go-angular ├── main.go └── view ├── README.md ├── angular.json ├── e2e ├── karma.conf.js ├── node_modules ├── package-lock.json ├── package.json ├── src ├── tsconfig.app.json ├── tsconfig.json ├── tsconfig.spec.json └── tslint.jsonプロジェクトをコピー
注意! 上記の「プロジェクト作成」で生成したプロジェクト以外に、ご自身で作成されたAngularプロジェクトがない場合は無視してください
ここで、生成された
view
ディレクトリ配下のsrc
ディレクトリを、既に作成済みのプロジェクトのsrc
ディレクトリに置き換えます。Angularプロジェクトをビルド
view
ディレクトリへ移動してビルドします。UHNaKZ:go-angular $ cd ./view UHNaKZ:go-angular/view $ ng buildすると、
go-angular ├── main.go └── view ├── README.md ├── angular.json ├── dist // NEW!! ├── e2e ├── karma.conf.js ├── node_modules ├── package-lock.json ├── package.json ├── src ├── tsconfig.app.json ├── tsconfig.json ├── tsconfig.spec.json └── tslint.jsonのようになったかと思います。
Goビルド
go-angular/main.go
を以下のように修正します。go-angular/main.gopackage main import( "github.com/gin-gonic/gin" ) func main(){ router := gin.Default() router.Static("/", "./view/dist/view") router.Run() }
修正できたらビルドします。UHNaKZ:go-angular $ go build最後に、生成されたバイナリファイルを実行します。
UHNaKZ:go-angular $ ./go-angular
localhost:8080
にアクセスすれば、Angularで作ったSPAが動いているのが分かると思います。ginを使わずに標準パッケージだけで書いたコード
main.gopackage main import ( "net/http" ) func main(){ ang := http.FileServer(http.Dir("./view/dist/view")) http.Handle("/",ang) http.ListenAndServe(":8080",nil) }ログが出ないです。
ログが出るようにすると少し長くなる。main.goimport ( "net/http" "fmt" "time" ) func main(){ http.HandleFunc("/",angSF) fmt.Println(" Your Angular application is runnning.") http.ListenAndServe(":8080",nil) } func angSF(w http.ResponseWriter, r *http.Request){ t := time.Now().Format("Mon Jan 2 15:04:05 -0700 MST 2006") fmt.Println(fmt.Sprintf("%s Status:%v Method:%s URL:%s",t,http.StatusOK,r.Method,r.URL)) http.ServeFile(w,r,"./view/dist/view") }ログこんな感じで作れば良いんかな。(ステータスは面倒なので200しか返さないです。)
まとめ
ginが楽。
- 投稿日:2020-09-25T13:42:39+09:00
Angular+ginでSPAを作る
目標
Angularで作ったSPA(シングルページアプリケーション)をginを使ってサーブします。
なぜginを使うか。
- ログがわかりやすいから。
- 記事が見当たらなかったから
前提
Angularのチュートリアルを通していること。
チュートリアル程度のAngular CLIが使えること。
既に、ご自身で作成されたAngularのプロジェクトがあるとします。
ご自身で作成されたAngularプロジェクトがない場合:
Angularプロジェクトをコピーを無視して進めてください。
デフォルトで生成されるSPAを表示することができます。手順
プロジェクト作成
始めのプロジェクト構成は以下のようにしています。
go-angular └── main.go
go-angular
ディレクトリに移動して、Angularプロジェクトを作成します。UHNaKZ:go-angular $ ng new view ? Would you like to add Angular routing? Yes // Yesを入力 ? Which stylesheet format would you like to use? CSS // CSSを選択
プロジェクトの構成が以下のようになります。
go-angular ├── main.go └── view ├── README.md ├── angular.json ├── e2e ├── karma.conf.js ├── node_modules ├── package-lock.json ├── package.json ├── src ├── tsconfig.app.json ├── tsconfig.json ├── tsconfig.spec.json └── tslint.jsonAngularプロジェクトをコピー
注意! ご自身で作成されたAngularプロジェクトがない場合は無視してください
ここで、生成された
view
ディレクトリ配下のsrc
ディレクトリを、既に作成済みのプロジェクトのsrc
ディレクトリに置き換えます。Angularプロジェクトをビルド
view
ディレクトリへ移動してビルドします。UHNaKZ:go-angular $ cd ./view UHNaKZ:go-angular/view $ ng buildすると、
go-angular ├── main.go └── view ├── README.md ├── angular.json ├── dist // NEW!! ├── e2e ├── karma.conf.js ├── node_modules ├── package-lock.json ├── package.json ├── src ├── tsconfig.app.json ├── tsconfig.json ├── tsconfig.spec.json └── tslint.jsonのようになったかと思います。
Goビルド
go-angular/main.go
を以下のように修正します。go-angular/main.gopackage main import( "github.com/gin-gonic/gin" ) func main(){ router := gin.Default() router.Static("/", "./view/dist/view") router.Run() }
修正できたらビルドします。UHNaKZ:go-angular $ go build最後に、生成されたバイナリファイルを実行します。
UHNaKZ:go-angular $ ./go-angular
localhost:8080
にアクセスすれば、Angularで作ったSPAが動いているのが分かると思います。ginを使わずに標準パッケージだけで書いたコード
main.gopackage main import ( "net/http" ) func main(){ ang := http.FileServer(http.Dir("./view/dist/view")) http.Handle("/",ang) http.ListenAndServe(":8080",nil) }ログが出ないです。
ログが出るようにすると少し長くなる。main.goimport ( "net/http" "fmt" "time" ) func main(){ http.HandleFunc("/",angSF) fmt.Println(" Your Angular application is runnning.") http.ListenAndServe(":8080",nil) } func angSF(w http.ResponseWriter, r *http.Request){ t := time.Now().Format("Mon Jan 2 15:04:05 -0700 MST 2006") fmt.Println(fmt.Sprintf("%s Status:%v Method:%s URL:%s",t,http.StatusOK,r.Method,r.URL)) http.ServeFile(w,r,"./view/dist/view") }ログこんな感じで作れば良いんかな。(ステータスは面倒なので200しか返さないです。)
まとめ
ginが楽。
- 投稿日:2020-09-25T13:14:09+09:00
【廃止】昔のデザインの YouTube に戻すユーザースクリプト作った(誰得)
追記
ということで、昔のデザインは完全に廃止されました。
R.I.P.
ついでにこの記事も死にました。
R.I.P.って、眠れないですよまったく…。
w3mから見れないじゃないですか。とりあえずこの記事は歴史的資料として遺します。
はじめに
昔、Google Chrome で YouTube を開こうとしたら Polymer(Google 製 JS ライブラリ)のエラーで見れませんでした。
そのとき調べていると、disable_polymer=1
というクエリパラメータを渡せば Polymer を用いない昔のデザインに戻せるということを知りました。ユーザースクリプト
そして、暇だったのでユーザースクリプトを書きました。
// ==UserScript== // @name Classic YouTube // @namespace https://qiita.com/okayurisotto/items/1ca3caffd55d787c39a8 // @version 1.0 // @description 昔のデザインのYouTubeに戻します // @author okayurisotto // @match *://www.youtube.com/* // @exclude *://www.youtube.com/embed/* // @run-at document-start // @grant none // ==/UserScript== (() => { 'use strict'; const url = new URL(location.href); const key = 'disable_polymer'; if (!url.searchParams.has(key) || url.searchParams.get(key) !== '1') { url.searchParams.set(key, '1'); location.replace(url.href); } })();おわりに
新しいデザインのほうが、ダークモードがあるので私は好きです。
ただ、古いデザインのほうがユーザースタイルシートでカスタマイズしやすいため、独自のダークモードを導入できますけどね。
- 投稿日:2020-09-25T13:11:44+09:00
簡単!JavaScript での URL 操作・検索パラメータ操作
はじめに
常識かもしれませんが、とても感動したので。
new URL()
URL インターフェイスは、URL オブジェクトを生成するための静的なメソッドを提供するオブジェクトを表します。
MDNよりこれを使えば、文字列として取得した URL をパースし欲しい値だけを取得することができる他、相対パスを絶対パスに変換することなどもできます。
文字列をhref
やsrc
のような感じに扱うことができるようになります。
それどころか URL インターフェイスのほうが扱いやすい場合もあるのでnew URL(location.href)
みたいなことをすることもしばしば。
new URLSearchParams()
こちらは、名前からだいたい想像がつくように、URL の検索パラメータの操作に特化したインターフェイス。
URLSearchParams インターフェイスは URL のクエリー文字列の操作に役立つメソッドを定義します。
MDNより
.append()
とか.delete()
とか.sort()
とか、いろいろと楽しそうなものがあるなか、最も興奮したのが、new URLSearchParams({ foo: 1, bar: 2 });これを
toString()
してしまえばもう?foo=1&bar=2
になるっていうんだからすごい楽。また、これには
new URL(str).searchParams
からもアクセスできます。おわりに
幸せ。
これらを知らなかったら、いちいち
document.createElement('a')
していたでしょう…。
知ることができて本当に良かったです。
- 投稿日:2020-09-25T12:47:51+09:00
JavaScript 実務必須知識
DOMについて
※わかりやすいサイト
https://note.com/skipla/n/nb65a0bb4c24c
- 投稿日:2020-09-25T12:11:12+09:00
【Gatsby.js】jsonファイルのデータをGraphQL経由で取得する
この記事ではjsonファイルからGraphQL経由でデータを取得し、ページ内で利用する方法を紹介します。
jsonファイルを元に、複数ページを生成する方法を知りたい場合は昨日書いた以下の記事を参照。
【Gatsby.js】単一のjsonファイルを元に複数のページを生成する - Qiitajsonファイルを配置
$ mkdir src/data $ touch src/data/events.jsonsrc/data/events.json[ { "title": "Gatsby勉強会", "date": "2020/10/01" }, { "title": "React勉強会", "date": "2020/11/01" }, { "title": "JavaScript勉強会", "date": "2020/12/01" } ]プラグインをインストール
$ yarn add gatsby-transformer-json gatsby-source-filesystem
gatsby-config.jsmodule.exports = { plugins: [ `other-plugin-hoge`, //追記↓ `gatsby-transformer-json`, { resolve: `gatsby-source-filesystem`, options: { name: `data`, path: `${__dirname}/src/data/`, }, }, ], }クエリを作成
gatsby develop
で開発サーバーを起動し、http://localhost:8000/___graphql?query にアクセスしGraphiQLを開く。
まずはroot?にallEventsJson
という項目が追加されていることを確認。
allEventJson > edges > node
を開き、title
とdate
にチェックを入れてクエリを発行。データを使いたいページ内で、データを取得
ここでは
src/pages/events.js
というページでデータを利用します。$ touch src/pages/events.jssrc/pages/events.jsimport React from "react" import { graphql, useStaticQuery } from "gatsby" export default () => { const data = useStaticQuery(graphql` query { allEventsJson { edges { node { date title } } } } `) const events = data.allEventsJson.edges return ( <div> {events.map(e => ( <div> <h2>{e.node.title}</h2> <p>{e.node.date}</p> </div> ))} </div> ) }結果
参考
- 投稿日:2020-09-25T12:07:08+09:00
未経験からフロントエンドエンジニアとして自社開発企業へ転職するまでにやったこと
はじめに
こんにちは☺
このたび、働きながら独学5ヶ月でフロントエンドエンジニアとして自社開発企業へ転職することができました。
今回は、学習開始から転職活動〜内定までを振り返ってみたいと思います。
フロントエンドエンジニアの転職活動についてまとめたものをあまり見なかったので、誰かの参考になれば嬉しいです。わたしの経歴について
地方国立大学文系学部卒業後、法律事務所勤務を経て地方公務員として働いています。
学生の頃、趣味でホームページを作成した経験はあるものの、HTML/CSS以外のプログラミング言語についてはまったく知らない状態でした。準備(2020年4月頃)
まずはじめに以下の環境を整えました。
・PCを購入(MacBook Air)
・VScodeインストール
・iTerm2インストール
・学習用Twitterアカウント開設また、短期目標として下記を設定しました。
・5月でインプット
・6〜7月でポートフォリオ作成
・8月〜転職活動開始、年内に転職先決定1ヶ月目(2020年5月):Webデザイン&コーディング
前述のとおりホームページ作成した経験もあって最初はWebデザイナーに興味があり、Webデザインとコーディングの学習から始めました。
書籍やUdemyでインプットし、まずはサイト模写やバナートレースなどのアウトプットを行いました。また、学習用に開設したTwitterアカウントで日々の積み上げやトレースしたバナーをつぶやいたり、自分と同じように勉強している方を見つけたりしてモチベーションを上げていました。
2ヶ月目(2020年6月):アプリ開発、ポートフォリオ検討
ネット上のチュートリアルを参考に簡単なアプリ開発を始めました。
この頃から、Webデザインよりアプリ開発のほうが楽しいと感じ、目標をWebデザイナーからフロントエンドエンジニアに変更し、学習内容のプランとポートフォリオの内容についてプランを練り直しました。3ヶ月目(2020年7月):ポートフォリオ作成
未経験から転職活動を行うにあたり、学習の成果や技術レベルを客観的に証明できるポートフォリオは必須です。
(実際に選考でもポートフォリオ必須の企業はいくつかありましたし、しっかりポートフォリオを作成したことで高評価をいただくこともありました。)フロントエンドエンジニアのポートフォリオについてはあまり情報がありませんでしたが、
①ログイン認証のない、誰でも気軽に利用できるアプリ
②ログイン認証があり、データベースへのCRUD処理ができるアプリ
③ポートフォリオサイト
の3つを作成することに決め、アプリのアイデアから考えました。アプリのアイデアについて
「なにか困っていることを解決したい」というような自分の”想い”が大切だと思います。
わたしは、”誰もが簡単に発言できる時代だからこそ、ひと手間かけて相手に気持ちを伝えることの大切さ”や”嬉しいできごとを共有して皆がHappyになってほしい”という想いから
①のアプリについては嬉しい出来事をカードにしてTwitterでシェアできるアプリ
②のアプリは嬉しい出来事をユーザーで投稿して共有できるアプリ
を開発しました。
(①については嬉しい出来事だけでなく誕生日などのお祝いや感謝の気持ちも伝えられるカードアプリを目標としていました。現在、追加実装中です・・)アプリの使用言語について
JavaScriptのフレームワークやライブラリであるVue.jsやReactなどを使用するのがよいと思います。
わたしは
・日本語の公式ドキュメントが充実している
・学習コストが低い
などの理由からVue.jsを選びました。
バックエンドについてはBaaS(Backend as a Service)であるFirebaseを利用しました。コードはすべてGitHubへ
ポートフォリオで作成したアプリのコードはすべてGitHubにpushしました。
選考の段階でGitHubのURLを送ってほしいと依頼されることもあったので、すべてpushしておいたほうがよいと思います。4ヶ月目(2020年8月)
ポートフォリオが完成したため、さっそく転職活動開始。
わたしの場合は地域を限定していたので、勤務希望地とフロントエンドエンジニアで検索して、実務経験必須の求人にも応募しました。転職活動中の学習について
転職活動中も、アプリの機能修正や書籍を読んだりしました。
また、面接で聞かれた技術的な質問でわからなかったことは必ずすぐ調べ、まとめるようにしました。5ヶ月目(2020年9月)
転職活動開始から1ヶ月、面談や面接は5社ほど受け、第一希望の企業より内定をいただきました。
学習に使用した教材やサービスについて
基本的に書籍とUdemyを利用しインプットして、チュートリアル等でコードを書いていくようにしました。
仕事の休み時間などのスキマ時間はQiitaの記事を読んだり、そのときわからないことをググってまとめるようにしています。Udemy
幅広い講座の中から、目的や好みに合った講座を購入できます。
セール時の購入がオススメです。書籍
◯1冊ですべて身につくHTML&CSSとWebデザイン入門講座(Mana著)
第一歩はここから。
◯JavaScript本格入門
THE入門書。基礎が大事なので、理解できないところは時間をかけてでも。
ポートフォリオ作成と並行してもOKだと思います。◯開眼!JavaScript
こちらも上記書籍と同様に。
◯Vue.js入門
Udemy講座終了後、ポートフォリオアプリ開発中こちらで復習しました。
◯Vue.jsのコツとツボがゼッタイにわかる本
こちらも復習用に読みました。
◯Webを支える技術
Webサービス設計の基礎について学ぶことができます。
◯リーダブルコード
独学でコードを書くことに不安があったため読みました。
読みやすいコードはどうあるべきか?さまざまなパターンが紹介されています。◯安全なWebアプリケーションの作り方
選考中の企業のエンジニアの方からお勧めされた本。
転職活動中に読みました。公式ドキュメント
コードを書いていてわからなければまず公式ドキュメントを確認していました。
ドットインストール
JavaScript〜ざっとスキマ時間を利用して復習で使用しました。
2分程度の動画の講座なので、ここから入るのも理解しやすいと思います。MENTA!
自分に合ったメンターさんと契約できるサービスです。
コードレビューや転職サポート(職務経歴書の添削など)をしてもらったり、メンターさんによってはチーム開発にも参加できるので、独学の方は利用をオススメします!Youtube
しまぶーのIT大学さんのYouTube講座は全体像がわかりやすく、オススメです!
転職活動について
利用した転職エージェント
Wantedly
「まずは話を聞きたい」から企業に連絡をとることができるので、カジュアル面談から入るところが多く気軽に話を聞きやすい。
Green
初回応募時に選考理由等フォーム入力するため敷居が高い印象だが、そのぶん初回応募で通ると面接に進みやすい。
履歴書、職務経歴書について
まず面接の前に提出を求められることが多いため、履歴書と職務経歴書も用意しておきます。
面接に向け、志望動機もより掘り下げて考えるようにしました。
できれば第三者に見てもらって意見をもらい、校正したほうがよいと思います。面接事前準備について
企業理念や社風に共感できるか、魅力を感じるかを重要視していたため、その点を重点的に調べました(ここは人によると思います)。
企業や業界研究をする中で、自己分析が進み、志望動機がより明確になりました。まとめ
未経験からエンジニア転職は難しいと言われていますが、わたしのような文系未経験30代でも自社開発企業に転職することができました!
転職活動にあたって、
・フロントエンドエンジニア志望でもしっかりポートフォリオを作成すること
・自己分析を行い、過去の職歴に基づく強みをアピールすること
が重要だと思いました。
また、完全独学だったので、早めにメンターサービスを利用してもよかったなと思います。いつ転職を目指すか、どのような学習方法が合っているかは個人によって異なりますので、自分に合ったやり方が一番です。
わたしの場合は転職先決定までの期間を短く設定していますが、長期目標として基礎をしっかり身につけた上でポートフォリオ作成してもよいと思いますし、いきなり正社員でなくともインターンをしながら学習するのもよいと思います。
ただ、やると決めたら徹底的にやること、それだけです!少しでも参考になれば嬉しいです☺
- 投稿日:2020-09-25T11:08:10+09:00
three.jsで表示するJSONファイルを手書きするときの最低限の要素
はじめに
Spectaclesという、Webブラウザ上で簡単に3Dデータを表示できるthree.jsをもとにしたJavaScriptのライブラリがあります。「BIM」とか「AEC」とか書いてあるので建築業界向けのやつですね。
これを使うにあたっては基本的には上記URL内で誘導されているRevit、GrasshopperのExporterを使えば良いのですが、中身そもそもどうなってんの?ということでJSONファイルを自分で書いたときのメモです。(手書きといっても出力にはコードを書きました)
3D全然分からない者が最低限何かの物体が表示できれば…というものなのでガラスの透明感の表現などの領域には及んでいません、すみません。JSONファイルの構成
GitHubのthree.jsのwikiでJSONのフォーマットが公開されています。(「Work in progress.」と書いてあるので途中っぽいですが)
3D初心者のためこれだけだと何が何やら分からなかったので中身の把握をやっていきます。
大まかに「geometries」「materials」「object」の三つで構成されています。geometries
物体一つ一つの座標と形状の定義です。
「geometries」項目の中に配列の形で個々の図形が記述されます。
{"geometries":[{図形の情報},{図形の情報},{図形の情報}]}
図形の情報は以下の形で書いていきます。"geometries": [ { "uuid":"一意のID", "type":"Geometry", "data": { "vertices":[X0,Y0,Z0,X1,Y1,Z1,...,Xn,Yn,Zn], "normals": [], "uvs": [], "faces": [0,0,1,2,0,1,2,3,...,0,0,4,5], "scale":1.0, "visible":true, "castShadow":true, "receiveShadow":false, "doubleSided":true } }, {図形の情報}, ・・・ ,{図形の情報} ]重要な情報は「uuid」「vertices」「faces」の三つです。
これ以外の項目は上の通りで構いません。
・uuid
一意のUUIDです。ここで宣言したUUIDを使って「object」内でこの図形を呼び出し、図形を表示させます。
・vertices
[0点目のX,0点目のY,0点目のZ,1点目のX,1点目のY,1点目のZ,...,n点目のX,n点目のY,n点目のZ]
のように3つごとの塊で認識される実数値の配列です。
負の値も大丈夫です。
Y座標が高さ、Z座標が奥行きとなっています。
・faces
[0,三角形の頂点1,三角形の頂点2,三角形の頂点3,0,三角形の頂点1,三角形の頂点2,三角形の頂点3,...,0,三角形の頂点1,三角形の頂点2,三角形の頂点3]
のように4つごとの塊で認識される三角形の頂点の配列です。(最初これが分かりませんでした)
それぞれの三角形の頂点の番号は「vertices」で定義した頂点のインデックスになっています。
直方体を描いた場合は実際こういう感じで三角形ごとに切れているということです。3D表現を知っている人には常識だそうです。
materials
各物体に割り当てる色や模様、質感の定義です。今回は表示だけできればいいので色についてだけ扱います。
「materials」項目の中に配列の形で個々のマテリアル情報が記述されます。
{"materials":[{マテリアル},{マテリアル},{マテリアル}]}
マテリアル情報は以下の形で書いていきます。"materials": [ { "uuid":"一意のUUID", "type":"MeshLambertMaterial", "color":9895680, "ambient":9895680, "emissive":0, "opacity":1.0, "transparent":false, "wireframe":false, "shading":1 }, {マテリアル},・・・,{マテリアル} ]ここでは「uuid」「color」「ambient」の三つを変更します。他の値は上の通りで構いません。
・ uuid
一意のUUIDです。ここで宣言したUUIDを使って「object」内で図形とこのマテリアルを関連付けます。
・color
OLE値で表現された色数値です。OLE値はRGB値を変換したもので、変換式はこちらで提示されており、以下の形になっています。
OLE = red + (green * 256) + (blue * 256 * 256)
RGB = "red % 255" + "(green / 256) % 256" + "(blue / 256 / 256) % 256"
上の例の「9895680」だとRGBでは「00ff96」で、明るめの緑ということになります。
・ambient
環境光の色です。
colorと同じ値にしましたが、別の色でもいいとは思います。個人的には同じ色にしておいた方が若干見やすいです。object
物体一つ一つをマテリアルと関連付けて表示するための定義です。
ここは他の二つと少し違い、{"object":{色々設定, "children":[{描画したい図形}, ・・・,{描画したい図形}]}}
という形になっています。設定部分
設定部分は以下の値を設定します。ここでは「uuid」「name」「layers」を変更します。
「uuid」はこれまでの通り一意のUUIDです。今回は他から参照してませんが念のため書いてます。消しても動作はします。
「name」はただの名前なので特に気を使う必要はありません。消しても動作はします。
「layers」ではレイヤとして使いたい名前のリストを「,」でそのまま繋げて記述します。「"A","B","C"」ではなく「"A,B,C"」となることに注意です。"object":{ "uuid":"一意のUUID", "name":"sample name", "type":"Scene", "matrix":[1.0,0.0,0.0,0.0,0.0,1.0,0.0,0.0,0.0,0.0,1.0,0.0,0.0,0.0,0.0,1.0], "userData":{"layers":"A,B,C"}, "children":[・・・] }children
ここで表示したい図形とマテリアルを関連付け、一つの物体にしていきます。
"children": [ { "uuid":"一意のUUID", "name":" ", "type":"RevitElement", "matrix":[1.0,0.0,0.0,0.0,0.0,1.0,0.0,0.0,0.0,0.0,1.0,0.0,0.0,0.0,0.0,1.0], "children":[ { "uuid":"描画したい図形のUUID", "name":" ", "type":"Mesh", "matrix":[1.0,0.0,0.0,0.0,0.0,1.0,0.0,0.0,0.0,0.0,1.0,0.0,0.0,0.0,0.0,1.0], "geometry":"描画したい図形のUUID", "material":"設定したいマテリアルのIIUD" } ], "userData":{ "sample_1":"40.0", "sample_2":"50.0", "sample_3":"0.9", "layer":"A" } },{},・・・,{} ]「children」の中にも「children」があったりして複雑です。ここでは自分自身の「uuid」を設定するほか、これまでに定義してきた「uuid」を使って物体の図形とマテリアルを関連付けています。
「type」で「RevitElement」と書いてるのは嘘ですね、手書きなので…。「type」を書かなくても動作はしましたが念のため残しています。
「userData」についてはSpectaclesの画面で各部品についての情報を確認するための設定です。各部品をクリック(黄色のハイライト)するとこんな感じで情報が表示されて便利です。最後の「layer」は上で設定した「layers」のうちの一つです。
まとめる
これまで書いたものをまとめると以下の形になります(長いので折り畳み)。文字コードはUTF-8です。
UUIDのつけ方はRevit Exporterに倣いました。
JSON全文
{ "geometries": [ { "uuid":"abef3476-2342-46f1-8931-81d22ab55256-MaterialNode_9895680_0", "type": "Geometry", "data":{ "vertices":[ 2220.0,0.0,20.0,2180.0,0.0,20.0,2180.0,0.0,-20.0,2220.0,0.0,-20.0,2220.0,430.0,20.0,2180.0,430.0,20.0,2180.0,430.0,-20.0,2220.0,430.0,-20.0 ], "normals": [], "uvs": [], "faces": [ 0,0,1,2,0,0,2,3,0,4,5,6,0,4,6,7,0,0,3,4,0,3,4,7,0,3,2,7,0,2,7,6,0,6,1,2,0,6,5,1,0,0,1,5,0,0,4,5 ], "scale":1.0, "visible":true, "castShadow":true, "receiveShadow":false, "doubleSided":true } }, { "uuid":"a0c57d79-188e-4315-9233-f2105a2f5262-MaterialNode_16774400_0", "type": "Geometry", "data":{ "vertices":[ 2220.0,0.0,630.0,2180.0,0.0,630.0,2180.0,0.0,590.0,2220.0,0.0,590.0,2220.0,430.0,630.0,2180.0,430.0,630.0,2180.0,430.0,590.0,2220.0,430.0,590.0 ], "normals": [], "uvs": [], "faces": [ 0,0,1,2,0,0,2,3,0,4,5,6,0,4,6,7,0,0,3,4,0,3,4,7,0,3,2,7,0,2,7,6,0,6,1,2,0,6,5,1,0,0,1,5,0,0,4,5 ], "scale":1.0, "visible":true, "castShadow":true, "receiveShadow":false, "doubleSided":true } }, { "uuid":"cfa8d5e4-d255-4b4b-924a-a92807a7ed49-MaterialNode_16743680_0", "type": "Geometry", "data":{ "vertices":[ 2200.55,428.2,0.0,2215.0,428.2,0.0,2215.0,430.0,0.0,2185.0,430.0,0.0,2185.0,428.2,0.0,2199.45,428.2,0.0,2199.45,383.0,0.0,2185.0,383.0,0.0,2185.0,381.2,0.0,2215.0,381.2,0.0,2215.0,383.0,0.0,2200.55,383.0,0.0,2200.55,428.2,610.0,2215.0,428.2,610.0,2215.0,430.0,610.0,2185.0,430.0,610.0,2185.0,428.2,610.0,2199.45,428.2,610.0,2199.45,383.0,610.0,2185.0,383.0,610.0,2185.0,381.2,610.0,2215.0,381.2,610.0,2215.0,383.0,610.0,2200.55,383.0,610.0 ], "normals": [], "uvs": [], "faces": [ 0,0,11,12,0,11,12,23,0,0,1,12,0,1,12,13,0,1,2,13,0,2,13,14,0,2,3,14,0,3,14,15,0,3,4,15,0,4,15,16,0,4,5,16,0,5,16,17,0,5,6,17,0,6,17,18,0,6,7,18,0,7,18,19,0,7,8,19,0,8,19,20,0,8,9,20,0,9,20,21,0,9,10,21,0,10,21,22,0,10,11,22,0,11,22,23,0,1,2,3,0,1,3,4,0,0,5,6,0,6,11,0,0,7,8,9,0,9,10,7,0,13,14,15,0,15,16,13,0,12,17,18,0,12,18,23,0,19,20,21,0,19,21,22 ], "scale":1.0, "visible":true, "castShadow":true, "receiveShadow":false, "doubleSided":true } } ], "materials": [ { "uuid":"MaterialNode_9895680_0", "type":"MeshLambertMaterial", "color":9895680, "ambient":9895680, "emissive":0, "opacity":1.0, "transparent":false, "wireframe":false, "shading":1 }, { "uuid":"MaterialNode_16774400_0", "type":"MeshLambertMaterial", "color":16774400, "ambient":16774400, "emissive":0, "opacity":1.0, "transparent":false, "wireframe":false, "shading":1 }, { "uuid":"MaterialNode_16743680_0", "type":"MeshLambertMaterial", "color":16743680, "ambient":16743680, "emissive":0, "opacity":1.0, "transparent":false, "wireframe":false, "shading":1 } ], "object": { "uuid":"4bc6463b-9f4f-4705-afeb-89d8f71dfecd", "name": "sample name", "type":"Scene", "matrix":[1.0,0.0,0.0,0.0,0.0,1.0,0.0,0.0,0.0,0.0,1.0,0.0,0.0,0.0,0.0,1.0], "userData":{"layers":"柱,梁"}, "children": [ { "uuid":"abef3476-2342-46f1-8931-81d22ab55256", "name":" ", "type":"RevitElement", "matrix":[1.0,0.0,0.0,0.0,0.0,1.0,0.0,0.0,0.0,0.0,1.0,0.0,0.0,0.0,0.0,1.0], "children":[ { "uuid":"abef3476-2342-46f1-8931-81d22ab55256-MaterialNode_9895680_0", "name":" ", "type":"Mesh", "matrix":[1.0,0.0,0.0,0.0,0.0,1.0,0.0,0.0,0.0,0.0,1.0,0.0,0.0,0.0,0.0,1.0], "geometry":"abef3476-2342-46f1-8931-81d22ab55256-MaterialNode_9895680_0", "material":"MaterialNode_9895680_0" } ], "userData":{ "H(cm)":"40.0", "B(cm)":"40.0", "layer":"柱" } }, { "uuid":"a0c57d79-188e-4315-9233-f2105a2f5262", "name":" ", "type":"RevitElement", "matrix":[1.0,0.0,0.0,0.0,0.0,1.0,0.0,0.0,0.0,0.0,1.0,0.0,0.0,0.0,0.0,1.0], "children":[ { "uuid":"a0c57d79-188e-4315-9233-f2105a2f5262-MaterialNode_16774400_0", "name":" ", "type":"Mesh", "matrix":[1.0,0.0,0.0,0.0,0.0,1.0,0.0,0.0,0.0,0.0,1.0,0.0,0.0,0.0,0.0,1.0], "geometry":"a0c57d79-188e-4315-9233-f2105a2f5262-MaterialNode_16774400_0", "material":"MaterialNode_16774400_0" } ], "userData":{ "H(cm)":"40.0", "B(cm)":"40.0", "layer":"柱" } }, { "uuid":"cfa8d5e4-d255-4b4b-924a-a92807a7ed49", "name":" ", "type":"RevitElement", "matrix":[1.0,0.0,0.0,0.0,0.0,1.0,0.0,0.0,0.0,0.0,1.0,0.0,0.0,0.0,0.0,1.0], "children":[ { "uuid":"cfa8d5e4-d255-4b4b-924a-a92807a7ed49-MaterialNode_16743680_0", "name":" ", "type":"Mesh", "matrix":[1.0,0.0,0.0,0.0,0.0,1.0,0.0,0.0,0.0,0.0,1.0,0.0,0.0,0.0,0.0,1.0], "geometry":"cfa8d5e4-d255-4b4b-924a-a92807a7ed49-MaterialNode_16743680_0", "material":"MaterialNode_16743680_0" } ], "userData":{ "H(cm)":"48.8", "B(cm)":"30.0", "t1(cm)":"1.1", "t2(cm)":"1.8", "layer":"梁" } } ] } }
上のコードで表示できたもの
おまけ(Revit Exporter)
2020年9月現在、Revit Exporterは最近のRevit(少なくともRevit 2019)に対応していないようです。
usingを一か所修正してif文を一行追加したら使えるようになったのでこれも記録しておきます。SpectaclesExportContext.cs//using Autodesk.Revit.Utility ←この行を下の行に修正 using AUtodesk.Revit.DB.VisualCommand.csforeach (var fi in collector){ if(fi.Category == null) continue; //←この行を追加 string category = fi.Category.Name;ちなみにRevitの標準サンプルファイルrac_advanced_sample_projectにExporterを使って変換したものをSpectaclesで表示するとこんな感じです。
ガラスもちゃんと透けてます。すごいですね。最後に
これをさらに頑張って色々していけばあんな感じになっていくんだな…と思うと果てしないですね。大人しくExporter使います。
- 投稿日:2020-09-25T08:01:37+09:00
JavaScript で複数の配列で重複したデータのみを取得するだけ
はじめに
数学的に言うと共通部分、Intersection のことです。
AND 検索とかで使えるかもしれません。JavaScript と TypeScript
const getIntersection = (baseArray, ...arrays) => { return baseArray.filter((value) => { return arrays.every((array) => { return array.includes(value); }); }); };const getIntersection = <T>( baseArray: Array<T>, ...arrays: Array<Array<T>> ) => { return baseArray.filter((value) => { return arrays.every((array) => { return array.includes(value); }); }); };
getIntersection
関数に引数として複数の配列を渡して使います。console.log(getIntersection([0, 1, 2, 3, 4, 5], [5, 6, 7, 8, 9, 0])); // [0, 5]引数の 1 つ目を
baseArray
として、それをfilter
していくという形です。
残りの配列すべてにbaseArray
の値(value
)が含まれていた場合true
となり、そうでない場合はfalse
となりbaseArray
から取り除かれます。おわりに
配列操作大好き!
- 投稿日:2020-09-25T01:10:37+09:00
Iterableなオブジェクトを配列風に扱うためのライブラリを作った
Iterableなオブジェクトを配列風に扱うためのライブラリを作った
JS標準オブジェクトの
Array
風にIterable
なオブジェクトを操作するライブラリ(Iteration-JS
)を作ってnpmで公開しました。
このライブラリの持っている機能は先行で作ってる人が千人くらいはいると思います。1 2
型定義ファイルを同梱しているのでTypeScript環境でも利用可能です。
大元のソースはTypeScriptで書いてあるのでDeno環境からなら直接インポートすることもできます。リポジトリ(GitHub,npm)
ソースコードはGitHubのリポジトリで管理しています。
ルート階層にソースが大量に置いてありますが、これはDeno環境などから直リンクで取得しやすい様にする工夫です。3https://github.com/felis392/iteration-js/
https://www.npmjs.com/package/@felis392/iteration-jsIssueを上げてもらえれば対応するかもしれません。
機能の一例
リポジトリのREADMEを読んでもらえば全部書いてあるのでここでは一部だけ抜粋します。
中間操作でArray
は作られないので入力が100万件あってもメモリ消費は小さく抑えられます。
Iteration
クラスはItrableプロトコルを実装しているので必要があればArray.from()
で配列を得ることもできます。import { Iteration, rangeClosed, iterate } from '@felis392/iteration-js'; const lcm = Iteration.on(rangeClosed(1, 1000000)) .filter(i => i % 17 === 0) .filter(i => i % 19 === 0) .filter(i => i % 23 === 0) .findFirst(i => i % 29 === 0); console.log(lcm); // 215441 const total = Iteration.on(rangeClosed(1, 10000)) .filter(i => i % 17 === 0) .filter(i => i % 19 === 0) .reduce((r, e) => r + e, 0); console.log(total); // 150195 let i = 0; for (const n of iterate(0, n => n < 10000000000000, n => n + 1)) { i += n; if (i >= 9000) { console.log(`n = ${n} i = ${i}`); break; } } // n = 134 i = 9045実装の解説
この記事のここから先は興味がある人向けです。ライブラリの利用がしたいだけならば読まなくても問題ありません。
ソースの全体はGitHubの方を見てもらえば分かるのでここでは一部のソースを例に解説を加えます。ジェネレータ関数を活用する
下記の関数では1行目でいきなりreturn文を書いていますが、この関数は
Iterable
の実体としてGenerator
を返しています。
GeneratorFunction
が欲しい訳ではないのでジェネレータ関数式はその場で実行しています。4 5
始端操作と中間操作はこの様にジェネレータ関数を活用して実装しています。iterate.tsexport function iterate<T>( seed: T, hasNext: (v: T) => boolean, next: (v: T) => T ): Iterable<T> { return function* (seed, hasNext, next) { for (let v = seed; hasNext(v); v = next(v)) yield v; }(seed, hasNext, next); }委譲を多用する
ライブラリ内で定義済みの関数は別の関数の実装に利用できる場面であれば積極的に利用し、処理を委譲しています。6
特にIteration
クラスは薄いラッパーに徹するためファクトリメソッド以外はすべて単品の関数を呼び出す形になっています。
単品の関数はどれも単独で使えるものです。利用したい場面が多そうな
range()
関数は以下の様に実装しています。
先程の説明で登場したiterate()
関数を呼び出す事で繰り返し処理の部分の実装を繰り返し行うのを回避しています。range.ts// @deno-types="./iterate.d.ts" import { iterate } from './iterate.js'; export function range( start: number, end: number ): Iterable<number> { start = Math.floor(start); end = Math.floor(end); return start < end ? iterate(start, i => i < end, i => i + 1) : iterate(start, i => i > end, i => i - 1); }単体テストを行う
TypeScriptをテストするツールはこれと言ったものが見つけられなかったため、ESモジュールに変換してからテストしています。7
こちらの記事で紹介されているtiny-esm-test-runnerを利用してテストを実施しています。8test-range.jsimport { range } from '../range.js'; import { assert } from 'tiny-esm-test-runner'; const { is } = assert; export function test_ToPositive() { const v = function*() { for (const i of range(8, 13)) yield i; }(); is(8, v.next().value); is(9, v.next().value); is(10, v.next().value); is(11, v.next().value); is(12, v.next().value); is(undefined, v.next().value); } export function test_ToNegative() { const v = function*() { for (const i of range(4, -3)) yield i; }(); is(4, v.next().value); is(3, v.next().value); is(2, v.next().value); is(1, v.next().value); is(0, v.next().value); is(-1, v.next().value); is(-2, v.next().value); is(undefined, v.next().value); }以上
JS標準のArrayの他にJava標準ライブラリのStream APIなども参考にして実装している。お手本があるのだから似たものは山程あるはず。 ↩
npmリポジトリにパッケージを公開してみたいという動機が先にあって、どうせなら実用的なものを作ろうという事でこの題材になった。 ↩
ディレクトリ構成はLoDashやUnderScoreJSを参考にした。それらがどういう理由で今の構成になっているのかまでは調べていない。 ↩
exportしないジェネレータ関数を外で定義しておけばいいので別に関数式にこだわる必要はない。イディオムとしてマイブームだったので多用しているだけだったりする。 ↩
見て分かる様にJavaの
Stream.iterate()
を真似して作った関数。冒頭の例の様にfor...of
文に投入するなら普通にfor
文書いた方が見易いのは言うまでもない。 ↩完全な最適化はしていないが、テストケースを書いてあるのでリファクタリングは気軽に行える。 ↩
JSにトランスパイルしてから行うならjestが使えそうだがESモジュールと相性が悪いのか設定の書き方が悪いのか上手く行かなかった。テストツールなんぞで悩みたくないので今回は気軽さ重視にした。 ↩
テストランナーがグローバルに注入してくるオブジェクトではなく明示的にアサーション用関数をimportするので非常に分かりやすい。 ↩
- 投稿日:2020-09-25T00:36:06+09:00
Blazor + canvas APIでアニメを描けるようにしてみた
はじめに
Blazorなどの学習を兼ねてアニメを描けるシンプルなお絵描きWebアプリを作りました。
Blazorを使っているとは口ばかりで、C#よりもTypeScriptを書いてることの方が多いです。
某謎アニ団さんぐらいアニメを描けたら良かったのですが、私では瞬きが限界です。現在は8fps(いわゆる三コマ打ち)のみですが、いずれはフレームレートを変更できるようにします。レートの変更だけなら楽ですが、アニメーション1秒の再生に24枚(24fps:日本のアニメの一般的なフレームレート)は普通描かないので、その空フレームに対する対応をする必要があります。
環境
Blazor WebAssembly 3.2.1 + .NET Standard 2.1
Microsoft.TypeScript.MSBuild 4.0.3
Firebase他、Blazorのテンプレートに内包されてたBootstrapなど。
実装について
Blazorと言いながらBlazorで行っているのはUIの制御だけです。
線の描き方
Canvas上で線を描く実装方法を探していたところ、
lineTo()
のメソッドを使用した例が多かったのですが、ペンを素早く動かした時に変な描き方になることがあり、ペンの軌道に合わせて円を置いていく形になりました。
ただし、そのままでは素早く描こうとしたときに線が切れてしまうので、以下のように微小な変化量を使って間を埋めるようにしています。しかし、現在では直線的な補完の仕方なので、もう少し曲線的な形を考えたい。数学を復習する必要がありそう。private prevX :number; private prevY :number; public drawLineWithPen(x:number, y:number, isDrawing:boolean){ let scaledX = x/this.scaleRate; let scaledY = y/this.scaleRate; if(!isDrawing) { this.context.beginPath(); this.context.moveTo(scaledX,scaledY); } else { // 分割数 let div = 200; let dx = (scaledX - this.prevX) / div; let dy = (scaledY - this.prevY) / div; let r = this.context.lineWidth/2; for(let i = 0; i<=div; i++){ let x = this.prevX + dx*i; let y = this.prevY + dy*i; this.context.beginPath(); this.context.moveTo(x,y); this.context.arc(x,y, r, 0, 2 * Math.PI, false); this.context.stroke(); this.context.fill(); this.context.closePath(); } } this.prevX = scaledX; this.prevY = scaledY; }ペンの入り抜きに関しては時間の差分をパラメータとして円の大きさや透明度を変えていくような形を考えています。
オニオンスキンの表示方法
オニオンスキンとは編集しているフレームの前後にあるフレームを特定の色で表示する機能です。今回は後方のフレームをピンク、前方のフレームを水色で表示しています。
あらかじめ保持しておいたImageDataの配列に対して、コピーを作り色の置換と透明度の設定を行っています。その後、Bitmapのデータを作成したのち、さらに
drawImage()
のメソッドを利用してオニオンスキン表示用のキャンバスへ書き込んでいます。ImageData
で直接canvasに書き込めれば良かったのですが、putImageData()
ではcanvasのすべてを置き換えてしまうのでこのような形になっています。private setOnionSkinsInternal(start:number, end:number, color:Color, frames: ImageData[], isPrev:boolean){ let startNum = Math.max(start, 0); let endNum = Math.min(end,frames.length); for(let i= startNum; i< endNum; i++) { let imageData = new ImageData(frames[i].data.slice(),frames[i].width,frames[i].height); for(let j=0;j<imageData.data.length; j+=4) { imageData.data[j] = color.r; imageData.data[j+1] = color.g; imageData.data[j+2] = color.b; // 現在のフレームから遠いフレームは透過度を強くして表示を薄くする if(isPrev) imageData.data[j+3] = imageData.data[j+3] * (i+1) / (endNum+1); else imageData.data[j+3] = imageData.data[j+3] * (startNum+1) / (i+1); } window.createImageBitmap(imageData).then( (img) => { // scale()で設定した倍率分、imageがさらに縮小されるので、倍率で割る? this.context.drawImage(img,0,0,this.width/this.scaleRate,this.height/this.scaleRate); } ).catch(() => { console.log(`${i} Error`)}); } }1枚あたり921,600かそれ以上のループが回るので、サイズを上げて10枚ほど表示しようとするとだいぶ重くなります。もう少し良い方法を考えたい。オニオンスキンの枚数分canvasを追加する……という方法も考えましたが試していません。
消しゴムとレイヤーの合成
https://hai3.net/blog/html5-canvas-eraser/
こちらの方法を参考にさせていただきました。ありがとうございます。
CanvasRenderingContext2D.globalCompositeOperation = "destination-out";
消しゴムでの描画時はこれを設定してあげればOKでした。https://developer.mozilla.org/ja/docs/Web/API/CanvasRenderingContext2D/globalCompositeOperation
MDNを読む限り、レイヤーの合成やペン先の水彩っぽさなどはこれを用いて実現できそうな感じがします。現在、消しゴムからペンへの切り替え方が、色を切り替えるかペン先サイズを変えるかしかないので、ちゃんとラジオボタン的な切替方法を作っておきたいと思います。
undo/redo
履歴保存用の
ImageData[]
を持っておいて、undo/redoメソッドが呼び出されたその都度PopやPushするだけです。履歴の保存に関してはTypeScript側で全て行うことも考えましたが、UI側で制御したいと思い、呼ぶタイミングはBlazor側に任せてあります。現状、実装が楽なので各フレーム毎にそれぞれ履歴を持つようになっていますが、フレームの削除を実装するタイミングで履歴の持ち方を変えそうです。public undo(){ if(this.currentFrame.prevHistory.length > 0) { let prev = this.currentFrame.prevHistory.pop() ?? new ImageData(this.width,this.height) ; this.currentFrame.nextHistory.push(this.getImageData()); this.putImageData(prev); } } public redo(){ if(this.currentFrame.nextHistory.length > 0){ let next = this.currentFrame.nextHistory.pop() ?? new ImageData(this.width,this.height); this.currentFrame.prevHistory.push(this.getImageData()); this.putImageData(next); } } public saveHistory(){ this.currentFrame.prevHistory.push(this.getImageData()); // 新しく履歴が追加された時、前方方向の履歴は削除する。 this.currentFrame.nextHistory = []; }canvas要素の優先順位
<canvas id="1"></canvas> <canvas id="2"></canvas> <canvas id="3"></canvas>イベントを拾うのが
id="1"
の場合、一番下に持っていかないとイベントが拾えません。TypeScript + dotnet CLI
dotnet add package Microsoft.TypeScript.MSBuild
のコマンドとjsonファイルの設定でdotnet run
した時などに.tsファイルもC#のコードと共にコンパイルされるので非常に楽でした。
Fableを使ってF#で書くのも考えたのですが、メソッド名の生成方法からメソッド名を指定しなければいけないJavaScript相互運用の仕様上厳しそうなので諦めました。今後追加する機能
GitHubのREADMEにやたらめったらと書きましたが、描き心地の問題は大きいので、線がカクついたり、入り抜きが一辺倒である問題は早めに解決したい。あとWeb Storage APIを使えば色やペンのサイズの設定値を保存できそうなので、そのあたりも対応していきたい。
タブレットやスマートフォン対応はやるとしてもだいぶ後の話になるでしょう。
私について
かつて、Windows Formアプリケーションを使ったシステム制作に関わっていたらしいニートです。
C#とCがほんの少しわかるだけで、TypeScriptなどの経験は特にありません。作っている意図
某お絵かきソフトは気軽にアニメーションを描いて動かせないし、アニメ制作するにしても使いにくいし、画面効果もないし、意外とこちら方面に目を向けたお絵描き(Web)アプリは無さそうだなという考えから。また、データベースの操作やログイン機能などがありませんが、機会・質などの問題は別として、どこかで誰かに見せたい時に見せられる用です。
元々はCanvas APIのお試しでアニメ機能などは考えていませんでしたが、作っているうちに機能を載せたくなってきて今に至ります。
おわりに
まだまだ実装したい機能や改善したい点など色々あるため、私のモチベーションが続く限りはやっていきたいですね。
TypeScriptはMicrosoftが開発に関わっているのもあり、C#に似ている部分があるのとdotnet CLIとの組み合わせもあって、コンパイル時にTypeScript側のエラーがある程度わかるのでだいぶ楽でした。Blazorも自身だけでブラウザのAPIに触れればいいのですが、IJSRuntimeを介してしかできないのでBlazor(C#)のみというのは厳しそうです。
- 投稿日:2020-09-25T00:31:48+09:00
Vuex + TypeScript + vuex-module-decoratorsでのmutationとgetterの単体テスト
やりたいこと
vuex-module-decoratorsを使うことで、TyepScriptでVuexを記述する際により型付けの恩恵を受けられるようになります。この記事ではvuex-module-decoratorsを使った際のVuexのmutationとgetterのJestでの単体テストの記述方法を紹介したいと思います。
以下の環境で動作検証しています。
- Vue.js: 2.6.19
- Vuex: 3.0.1
- vuex-module-decorators: 0.9.9
- 単体テストユニット: Jest
テスト対象のVuex
テスト対象として以下のVuexを作成します。
counter.tsimport { Action, Module, Mutation, VuexModule } from "vuex-module-decorators"; @Module({ name: "counter", namespaced: true }) export class Counter extends VuexModule { // state private count: number = 0; // mutaion // カウンターをインクリメントする @Mutation public increment(): void { this.count++; } // getter // カウンターの値を取得 get getCount() { return this.count; } // action // カウンターの値を2インクリメントするアクション @Action({}) public add2(): void { this.increment(); this.increment(); } }Jestでの単体テスト
以下のように単体テストを記述できます。ポイントとしては、Counter.mutations!.increment(mockState, {})のようにモック化したstateに対してmutation, getterを実行することでテストケースごとに別のステートを使用している点です。
テストケースごとにステートの値を変更できる点と、mutaionとgetterを独立してテストできる点がメリットです。
counter.spec.tsimport Vuex from "vuex"; import { Counter } from "@/store/modules/counter"; import { createLocalVue } from "@vue/test-utils"; import { cloneDeep } from "lodash"; import { Action, getModule } from "vuex-module-decorators"; // ローカルのVueインスタンを使用する const localVue = createLocalVue(); localVue.use(Vuex); describe("Counter test", () => { // mutationnのテスト it("mutation test increment1", () => { // ステートをモック化 const mockState = { count: 0 }; Counter.mutations!.increment(mockState, {}); expect(mockState.count).toBe(1); }); // mutationnのテスト it("mutation test increment2", () => { // ステートをモック化 const mockState = { count: 0 }; // テストケースごとに別のモック化したステートを使用できる expect(mockState.count).toBe(0); Counter.mutations!.increment(mockState, {}); Counter.mutations!.increment(mockState, {}); expect(mockState.count).toBe(2); }); // getterのテスト it("getter test", () => { // ステートをモック化、カウンターの初期値を変更 const mockState = { count: 3 }; const actions = Counter.actions!; expect(Counter.getters!.getCount(mockState, null, null, null)).toBe(3); }); });おわりに
actionも同様にモック化したstatenを使用してテストしたかったのですが、記述方法がわかりませんでした。ご存知の方がいらっしましたら教えていただけると嬉しいです。
- 投稿日:2020-09-25T00:20:32+09:00
JavaScriptで指定した期間の、月単位、日単位の連番を作成する
やりたいこと
datepicker等で選択した日付の範囲を、月単位、日単位で区切り配列にぶち込む。
'2020-08-17' から '2020-09-03' で期間指定したとき、
日単位の場合は素直に
['2020-08-17','2020-08-18','2020-08-19',...,'2020-09-03']
月単位の場合は、'yyyy-mm' 部分をみて
['2020-08','2020-09']が欲しい。
本題
今回は、期間は決め打ちで。
配列を用意してvar fromDate = '2020-08-17' var toDate = '2020-09-03' var monthlyRange = []; var daylyRange = [];for文で日、月を加算していくので、date型に変換
new Date('yyyy-mm-dd')
の形でOK。
参考:http://cly7796.net/wp/javascript/how-to-use-new-date/new Date('yyyy-mm')
だとddの部分は '01' になる。月の加算、配列格納はこちらを利用。// 指定した日付から年と月を取り出す。'yyyy-mm-dd' => 'yyyy-mm' regexp = new RegExp(/\d\d\d\d-\d\d/); var fromYM = fromDate.match(regexp)[0]; var toYM = toDate.match(regexp)[0]; // 日付の大小比較のため、date型に変換 var start = new Date(fromDate); var end = new Date(toDate); // ddを省略することで、その年月の一日のdateが手に入る var startYM = new Date(fromYM); var endYM = new Date(toYM);後は以下。
型変換は下で定義した関数で行う。// date型を文字列に変換し配列に格納 for(var ym = startYM; ym <= endYM; ym.setMonth(ym.getMonth()+1)) { var strYearMonth = convertToStr(ym, 0); monthlyRange.push(strYearMonth); } for(var d = start; d <= end; d.setDate(d.getDate()+1)) { var strDate = convertToStr(d, 1); daylyRange.push(strDate); } // date型を文字列に変換する function convertToStr(date, day){ // dayはフラグ。真のとき'yy-mm-dd'、偽のとき'yy-mm'。 var strDate; if(day){ strDate = [ date.getFullYear(), ('0' + (date.getMonth() + 1)).slice(-2), ('0' + date.getDate()).slice(-2) ].join('-'); }else{ strDate = [ date.getFullYear(), ('0' + (date.getMonth() + 1)).slice(-2), ].join('-'); } return strDate; }結果
["2020-08", "2020-09"] ["2020-08-17", "2020-08-18", "2020-08-19", "2020-08-20", "2020-08-21", "2020-08-22", "2020-08-23", "2020-08-24", "2020-08-25", "2020-08-26", "2020-08-27", "2020-08-28", "2020-08-29", "2020-08-30", "2020-08-31", "2020-09-01", "2020-09-02", "2020-09-03"]最適解ではないと思いますが、とりあえず思い通りです。