- 投稿日:2020-01-21T23:13:02+09:00
JavaScriptの関数のかきかた!
はじめに
やっと初投稿。1ヶ月前くらいにアカウントを作ってみたものの書くこともなく放置状態だった(正確には最初にこれから頑張る!的な投稿をしたが「それ技術系の記事じゃねーだろ」と運営に言われ非公開になっております)。今の状況としては、Web系エンジニアに必要なスキルの修得を目指してとりあえずProgateの各コースを進めているところ。その中で自分が慣れ親しんだCやJava(長くやってるだけでスキルが高いわけではない)とJavaScriptでは関数の定義の仕方が結構違う感じがしたのでメモ的な感じで投稿してみる。初投稿だしいいよねこんな感じで。
Cの関数
例えば、2つの整数を引数にもち、和を戻り値として返すadd関数を宣言してみる。
int add(int x,int y){ return x + y; }JavaScriptの関数
対して、JavaSciprtでこのadd関数を書くとこうなる。
const add = (x,y) => { return x + y; };まとめ
こうして並べてみるとやはりかなり違うことがわかる。引数のところに型名いらないし、関数を定数(変数)に代入してたり。言語が違うんだから書き方が違うのは当たり前なんだけど、CとJavaってここらへんの書き方はわりと似てたからまた一つ新しいことが学べていい気分。Webエンジニアへの道のりは果てしないけど頑張ろう・・・!
これからもこんな感じでゆる~く投稿していこうと思うのでどうぞよしなに。
- 投稿日:2020-01-21T23:12:30+09:00
Vue.js の基本的な機能を使ったサンプルを書く
概要
- Mustache 構文、条件付きレンダリング、メソッド、算出プロパティ、フォーム入力バインディング、リストレンダリング、コンポーネントの機能を使ったサンプルコードを示す
- 環境: Vue.js 2.6.11
Mustache 構文で Hello World
- 画面に Hello, world! と表示するサンプルコード
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Hello, world!</title> </head> <body> <div id="app"> <!-- Mustache 構文で message プロパティを表示 --> <p>{{ message }}</p> </div> <!-- デバッグに便利な Vue.js の開発バージョンを使う --> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script> // Vue インスタンスを生成 new Vue({ // Vue インスタンスが管理する DOM 要素 el: '#app', // プロパティ data: { message: 'Hello, world!' } }) </script> </body> </html>データバインディングのもっとも基本的な形は、”Mustache” 構文(二重中括弧)を利用したテキスト展開です:
条件付きレンダリング、メソッド、算出プロパティ
- 「カウントアップ」ボタンを押すとカウント数が1増える
- カウント数を表示する
- 3の倍数のときはカウント数ではなく「あほー」と表示する
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Counter</title> </head> <body> <div id="myCounterDiv"> <!-- DOM イベントをトリガーに設定 --> <button v-on:click="myCountUp">カウントアップ</button> <!-- データバインディング --> <!-- v-bind で title 属性にセット --> <!-- Mustache 構文で要素内に表示 --> <p v-bind:title="myMessage">カウンター: {{ myMessage }}</p> <!-- 条件付き描画 (Conditional Rendering) --> <p v-if="myCounter % 6 == 0">6の倍数ですね</p> <p v-else-if="myCounter % 3 == 0">3の倍数ですね</p> <p v-else-if="myCounter % 2 == 0">2の倍数ですね</p> <p v-else>2の倍数でも3の倍数でもないですね</p> </div> <!-- サイズと速度が最適化された Vue.js の本番バージョンを使う --> <script src="https://cdn.jsdelivr.net/npm/vue"></script> <script> // Vue インスタンスを生成 var vm = new Vue({ // Vue インスタンスが管理する DOM 要素 el: '#myCounterDiv', // プロパティ data: { myCounter: 0 }, // created フック // インスタンスが作成された後に同期的に呼ばれる created: function() { this.myCounter = 1 }, // メソッド methods: { myCountUp: function (event) { this.myCounter++ } }, // 算出プロパティ (computed properties) computed: { myMessage: function () { if (this.myCounter % 3 == 0) { return "あほー" // 3の倍数のときに返す値 } else { return this.myCounter } } } }) </script> </body> </html>v-on ディレクティブを使うことで、DOM イベントの購読、イベント発火時の JavaScript の実行が可能になります。
v-bind
1つ以上の属性またはコンポーネントのプロパティと式を動的に束縛します。
v-if ディレクティブは、ブロックを条件に応じて描画したい場合に使用されます。ブロックは、ディレクティブの式が真を返す場合のみ描画されます。
Vue インスタンスが作成されると、自身の data オブジェクトの全てのプロパティをリアクティブシステムに追加します。これらのプロパティの値を変更すると、ビューが”反応”し、新しい値に一致するように更新します。
各 Vue インスタンスは、生成時に一連の初期化を行います。例えば、データの監視のセットアップやテンプレートのコンパイル、DOM へのインスタンスのマウント、データが変化したときの DOM の更新などがあります。その初期化の過程で、特定の段階でユーザー自身のコードを追加する、いくつかの ライフサイクルフック(lifecycle hooks) と呼ばれる関数を実行します。
例えば、created フックはインスタンスが生成された後にコードを実行したいときに使われます。
算出プロパティの代わりに、同じような関数をメソッドとして定義することも可能です。最終的には、2つのアプローチは完全に同じ結果になります。しかしながら、算出プロパティはリアクティブな依存関係にもとづきキャッシュされるという違いがあります。
フォーム入力バインディング、リストレンダリング
- フォームに入力したテキストをリストに追加
- リストを表示
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>List</title> </head> <body> <div id="myListDiv"> <!-- フォーム入力バインディング --> <input v-model="myItem" placeholder="アイテム名"> <!-- DOM イベントをトリガーに設定 --> <button v-on:click="myAddItem">{{ myItem }} を追加</button> <!-- 配列を表示 --> <ul> <li v-for="item in myItemList"> {{ item.name }} </li> </ul> </div> <!-- Vue.js のバージョン 2.6.11 を使う --> <script src="https://cdn.jsdelivr.net/npm/vue@2.6.11"></script> <script> var vm = new Vue({ // Vue インスタンスが管理する DOM 要素 el: '#myListDiv', // データ data: { myItem: '', myItemList: [ { name: 'やくそう' }, { name: 'どくけーしー' } ] }, // メソッド methods: { myAddItem: function() { if (this.myItem !== '') { this.myItemList.push({name: this.myItem}) this.myItem = '' } } }, }) </script> </body> </html>form の input 要素 や textarea 要素、 select 要素に双方向 (two-way) データバインディングを作成するには、v-model ディレクティブを使用することができます。
配列に基づいて、アイテムのリストを描画するために、v-for ディレクティブを使用することができます。v-for ディレクティブは item in items の形式で特別な構文を要求し、items はソースデータの配列で、item は配列要素がその上で反復されているエイリアスです:
コンポーネント
- それぞれ別のカウント値を持ったカウンター
- ボタンを押すとカウント数が1増える
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Counters by Components</title> </head> <body> <div id="components-demo"> <!-- それぞれが別のインスタンスになるため、それぞれ別の count プロパティを保持する --> <button-counter></button-counter> <button-counter></button-counter> <button-counter></button-counter> </div> <!-- デバッグに便利な Vue.js の開発バージョンを使う --> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script> // button-counter という新しいコンポーネントを定義 // コンポーネントは再利用可能な Vue インスタンス Vue.component('button-counter', { // コンポーネントの data オプションは関数でなければならない data: function () { return { count: 0 } }, // Vue インスタンスに対して使用するテンプレート文字列 // クリックするとカウントアップ count++ template: '<button v-on:click="count++">You clicked me {{ count }} times.</button>' }) new Vue({ // Vue インスタンスが管理する DOM 要素 el: '#components-demo', }) </script> </body> </html>コンポーネントは再利用可能な Vue インスタンスなので、data、computed、watch、methods、ライフサイクルフックなどの new Vue と同じオプションを受け入れます。唯一の例外は el のようなルート固有のオプションです。
コンポーネントのプロパティ
- 表示情報のデータをコンポーネントに渡す
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Blog Posts by Components</title> </head> <body> <div id="components-demo"> <!-- 定義した blog-post コンポーネントを表示 --> <!-- v-bind:key で仮想 DOM 処理ヒント用にユニークなキーである myPost.id を指定 --> <!-- blog-post の post 属性に値 myPost を渡す --> <blog-post v-for="myPost in myPostList" v-bind:key="myPost.id" v-bind:post="myPost" ></blog-post> </div> <!-- デバッグに便利な Vue.js の開発バージョンを使う --> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script> // blog-post という新しいコンポーネントを定義 // コンポーネントは再利用可能な Vue インスタンス Vue.component('blog-post', { // データを受け取るためのプロパティ props: ['post'], // HTML 描画用テンプレート // Mustache 構文でタイトルを出力 // v-html で HTML をそのまま出力 template: ` <div class="blog-post"> <h3>{{ post.title }}</h3> <div v-html="post.content"></div> </div> ` }) new Vue({ el: '#components-demo', data: { myPostList: [ { id: 1, title: 'たいとる1', content: '<p>なかみ1</p>' }, { id: 2, title: 'たいとる2', content: '<p>なかみ2</p>' }, { id: 3, title: '>_<;', content: '<p>こうですか!? わかりません><</p>' } ] } }) </script> </body> </html>プロパティはコンポーネントに登録できるカスタム属性です。値がプロパティ属性に渡されると、そのコンポーネントインスタンスのプロパティになります。
参考資料
- 投稿日:2020-01-21T22:17:47+09:00
ページ遷移で、真っ白な画面が表示された
事象
Reactで作ったWebページをスマホで確認していたところ、とあるページ遷移で、真っ白な画面が表示された。
コード
fetch.jsvar controller = new AbortController(); fetch('https://hogehoge.com/send', { method: 'POST', signal: controller.signal, // controllerが持つsignalをfetchに渡す }).then((data) => { // 応答が得られた場合の処理 }).catch((error) => { controller.abort(); // fetchキャンセル });原因
「AbortController」をサポートしていないバージョンのChromeで読み込んだため。
この時のChromeのバージョンは「55」。
Chromeでの「AbortController」サポートは「66」以降になる。
https://developer.mozilla.org/en-US/docs/Web/API/AbortController
- 投稿日:2020-01-21T19:08:05+09:00
Blocklyに基づきビジュアルプログラミングの入門級の実例(三、VUE環境でBlocklyのコードを非同期的に処理するサンプル)
本章では、VUEのシンプルなサンプルで、BlocklyのBlockから生成コードを非同期的に実行する方法を説明しております。
Blocklyのコードをステップ毎に実行する時は、Interpreterライブラリをよく使っているんですけど、Interpreterライブラリを使いたくない場合は、JavaScriptのeval関数でBlocklyのコードを実行することも可能です。
evalは一般的に同期でソースコードを実行されているんで、非同期的に実行したい場合はどうしたらよいかについてのことを、以下のサンプルで説明して見ましょう。事前準備
Blockly
公式サイト:https://developers.google.com/blockly/
Web版の資材:https://developers.google.com/blockly/guides/get-started/web#get_the_codeVUE
https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.22/vue.min.js
サンプル
testblock.js
このファイルで、画面表示用のブロックを作成します
BlocklyのDevelopToolから生成することができます。Blockly.Blocks['block_asyncplay'] = { init: function() { this.appendStatementInput("MUSIC") .setCheck(null) .setAlign(Blockly.ALIGN_RIGHT) .appendField("play"); this.setInputsInline(true); this.setPreviousStatement(true, null); this.setNextStatement(true, null); this.setColour(120); this.setTooltip(""); this.setHelpUrl(""); } }; Blockly.JavaScript['block_asyncplay'] = function(block) { var statements_music = Blockly.JavaScript.statementToCode(block, 'MUSIC'); var code = "async function asyncplay() {\n" + statements_music + "\n}\n"; return code; }; Blockly.Blocks['block_play'] = { init: function() { this.appendDummyInput() .setAlign(Blockly.ALIGN_RIGHT) .appendField("play"); this.setInputsInline(true); this.setPreviousStatement(true, "String"); this.setNextStatement(true, "String"); this.setColour(120); this.setTooltip(""); this.setHelpUrl(""); } }; Blockly.JavaScript['block_play'] = function(block) { var code = "await play(1000);\n"; return code; };test01.html
画面にブロックを表示するためのHTMLファイルです。
<script src='https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.22/vue.min.js'></script><!-- 2019-01-25 https://cdnjs.com/libraries/vue --> <script src="../blockly-master/blockly_compressed.js"></script> <script src="../blockly-master/blocks_compressed.js"></script> <script src="../blockly-master/javascript_compressed.js"></script> <script src="../blockly-master/msg/js/en.js"></script> <script src="./testblock.js"></script> <body> <div id="vue_example"></div> <script> var vue_example = new Vue({ el: '#vue_example', template: `<div> <div width="600px" height="50px"> <button v-on:click="test()">test</button> </div> <div width="600px" height="600px"> <div id="blocklyDiv" style="height: 100%; width: 100%;"></div> <xml id="toolbox" ref="toolbox" style="display: none"> <block type="block_asyncplay"></block> <block type="block_play"></block> </xml> <xml id="workbox" ref="workbox"> <block type="block_asyncplay" id="id_block_asyncplay" x="10" y="30"> <statement name="MUSIC"> <block type="block_play" id="id_block_play_01"> <next> <block type="block_play" id="id_block_play_02"> <next> <block type="block_play" id="id_block_play_03"></block> </next> </block> </next> </block> </statement> </block> </xml> </div> </div>`, data: { message: 'Hello Vue.js!', }, mounted() { var workbox = this.$refs["workbox"]; var options = { toolbox: toolbox, collapse: true, comments: true, disable: true, maxBlocks: Infinity, trashcan: true, horizontalLayout: false, toolboxPosition: 'start', css: true, rtl: false, scrollbars: true, sounds: true, oneBasedIndex: true, grid: { spacing: 20, length: 1, colour: '#888', snap: true } } /* Inject your workspace */ this.workspace = Blockly.inject('blocklyDiv', options) //Workspaceに書かれたBlocksを表示 Blockly.Xml.domToWorkspace(workbox, this.workspace); }, methods: { test: function () { var dom = Blockly.Xml.workspaceToDom(this.workspace); console.log( Blockly.Xml.domToText(dom)); var code = Blockly.JavaScript.workspaceToCode(this.workspace); console.log( code ); eval(code); asyncplay(); async function play(waittime) { return new Promise((resolve, reject) => { console.log("play waittime:" + waittime); setTimeout(resolve, waittime, 'time signature'); }); } }, }, }) </script> </body> </html>実行結果
- 投稿日:2020-01-21T19:05:55+09:00
【Nuxt.js】 middleware is 何?
ミドルウェアとは
- ミドルウェアを使用すると、ページがレンダリングされる前(SSR処理などが行われる前)に設定された関数を実行することができる
- 認証許可が必要なページやログイン後のリダイレクトパスを保持するために使用される。
- 関数は
middleware/
ディレクトリに入れる
middleware/auth.js
はauth
ミドルウェアになる- ミドルウェアは第一引数に
context
を取る
context
の内容についてはこちらを参照- ユニバーサルモードの場合は、サーバーサイドで一度だけ呼び出される。 (Nuxtアプリケーションへの最初のリクエスト時、またはページの再読み込み時)クライアントサイドでは、他のルートへ移動した時に呼び出される。
- SPAモードの場合、クライアントサイドでの最初のリクエスト時と他のルートへ移動した時に呼び出される。
middlewareを使用した処理の例
storeのアカウント情報が無い場合、ログイン画面にリダイレクトされる処理を書いてみる。
middleware/redirect.jsexport default function ({ redirect, store }) { const user = store.state.user if(!user) { redirect('/login') } }これを
nuxt.config.js
で読み込む。nuxt.config.jsexport default { router { middleware: 'redirect' } }このようにすると全てのルート変更時に
redirect.js
が読み込まれるようになる。また、特定のページ(またはレイアウト)にのみ特定の関数を設定することもできる。
index.vue<script> import { fetchUid } from '@/middleware/uid.js' export default { middleware: [ 'auth', fetchUid ] } </script>1ファイルに1つの処理の場合は、'auth'で関数を実行することができる。
一方、1つのファイルに複数の関数がある場合は、ファイルをimportすることで特定の関数を実行することができる。middlewareの実行順序について
middleware
自体の実行順序は、このようになっている。
plugins
→middleware
→fetch
→asyncData
middleware
の関数の呼び出し方の違いによる実行順序はこのようになる。
nuxt.config.js
→layout
→page
Login画面では実行したくない問題
アカウント情報を確認してリダイレクトさせる処理を
middleware
内に書いた場合、Login画面ではまだアカウント情報がないので、関数を実行したくない場合がある。
その場合は、middleware
内のcontext
にroute
があるので、そこから関数を実行したくないページを弾いて処理を実行させるようにする。middleware/auth.jsexport default function ({ redirect, store, route }) { const user = store.state.user if(!user && route.path !== '/login') { redirect('/login') } }
- 投稿日:2020-01-21T18:47:11+09:00
async(Promise)のthen()内の関数の引数の省略
備忘録として書き記す。
nuxt.jsのコードを追っていたところ下記の部分で引っかかる。
.nuxt/client.js// Create and mount App createApp().then(mountApp).catch(errorHandler)コードの内容から察するにmountAppにはcreateAppの結果が引数として渡されているように見えるので実際に確かめてみた。
main.jsconst first = async () => { return 'first' } const second = (arg) => { console.log('second') console.log(arg) } // then内の関数に自動的に引数が渡される first().then(second) // second // first // 省略しないパターン first().then((f) => second(f))どなたかこのような振る舞いの名称や公式で記載されているページをご存知であれば、コメントで教えていただけると幸いです。
- 投稿日:2020-01-21T18:28:20+09:00
【Nuxt.js】pagination実践編:$router.pushで簡単実装!
前置き
ページ数に応じて
urlと表示が変わるpaginationです?
前回やった導入編と全く別物です!
こっちの方が簡単なので別パターンとして紹介?記事タイトルが紛らわしいので、
まとめて名称変えるかもしれません。
こちらの続きはまた別記事にて…!
https://note.com/aliz/n/n8bb439a426a8firebaseを月曜日に公開予定でしたが、
Cloud Firestoreがバグり。。。
それが落ち着いてからにします☁️
今後は火・木に投稿していく予定です!構成
・pagination部分をコンポーネント化
・使用するページからpropsでdataを渡す?
・ページ数をurlに表示させる?($router.push)
・全7ページで、ページ数に応じて表示を変更Step1: コンポーネントでpropsを用意
【構成】
使うコンテンツによって
最大ページ数などが変わるためpropsを使用
・query: ページネーションを使うコンテンツ
・length: ページの長さ
・now: 今いるページPagination.vue<script> export default { props: { query: { type: String, required: true, }, length: { type: Number, required: true, }, now: { type: Number, required: true, }, }, } </script>Step2: コンポーネントで戻る・進むボタンを追加
【式】三項演算を使用
式1 ? 式2 : 式3
式1がtrueなら式2、falseなら式3Pagination.vue<template> <div> <button class="btn btn-prev" @click="$router.push(`?${query}=${now - 1 || 1}`)" > 戻る </button> <button class="btn btn-next" @click="$router.push(`?${query}=${now + 1 <= length ? now + 1 : length}`)" > 進む </button> </div> </template> <script> export default { props: { query: { type: String, required: true, }, length: { type: Number, required: true, }, now: { type: Number, required: true, }, }, } </script>【解説】
◾️戻る
・|| または該当コンテンツページ内queryの
今いるページnowから1戻る、または1にする◾️進む
該当コンテンツページ内の
今いるページに1を足して
・全体ページ数と同じ
・またはそれより小さい場合
今いるページから1進む
そうでなければ全体ページ数にするつまり全体ページを7で、
現在いるページが7なら
7 + 1 <= 7
falseになるので7のまま
それ以上進むことはないですね?あれ??
【戻る】すごくシンプルに見えるのに…Pagination.vue@click="$router.push(`?${query}=${now - 1 || 1}`)"【進む】は何か長い。
Pagination.vue@click="$router.push(`?${query}=${now + 1 <= length ? now + 1 : length}`)"これではダメなの????
Pagination.vue@click="$router.push(`?${query}=${now + 1 || length}`)"最大ページ数を越えてどんどん進みます笑
lengthの値はマイナスにはできません。
そのため制限をかけなくても
勝手に1で止まってくれるのですが…!
プラスは制限をかけないと止まりません?♀️?Step3: コンポーネントでページ数を表示
【構成】
ページ数の表示部分を作りましょう!
・5ページまではページ数分のみ表示
・6ページ以上は…(三点リーダー)で中間を省略【CSS】
毎度のことですが省きます。
・…はcssでdotクラスでborderを使用
・現在ページがをクラスバインディングで
background-color, colorを変更?【if, if, if…】
ifで沢山分岐しています笑
どこで並列になってるか分かりにくいですね?
コンパクトにして全体構造を把握しましょう。【Pagination.vue】
主にインラインのコメントで解説!
コードでも並列部分を絵文字で区別しています。
?と?が並列で使われている部分です。
それ以外の絵文字は if の目印です!Pagination.vue<template> <div // ページ数が1より大きい、2ページ以上の時のみページネーションを表示 v-if="length > 1" class="list-item list-item-nav" > <button class="btn btn-prev" @click="$router.push(`?${query}=${now - 1 || 1}`)" > 戻る </button> <ul class="list"> // 1ページ目はどんな時でも固定表示のためif不要 <li // クラスバインディング、{ class名: 式 }でtrueの時にクラスがつく :class="{ now: now === 1 }" class="item item-link" // 1ページを押すとurlが~/1になる @click="$router.push(`?${query}=1`)" > <span class="text"> 1 </span> </li> // ?ここから分岐、最大ページ数が2より大きい3〜 <template v-if="length > 2"> // ?3以上5以下(=最大ページ数3,4,5の時) 5ページまでの場合は、最大ページ数に応じて該当ページ数を表示 <template v-if="length <= 5"> <li :class="{ now: now === 2 }" class="item item-link" @click="$router.push(`?${query}=2`)" > <span class="text"> 2 </span> </li> // ?最大ページ数が3, 4, 5かつ3より大きい4, 5の時 <template v-if="length > 3"> <li :class="{ now: now === 3 }" class="item item-link" @click="$router.push(`?${query}=3`)" > <span class="text"> 3 </span> </li> // ?最大ページ数が3, 4, 5かつ3より大きい4, 5かつ4より大きい5の時 <template v-if="length > 4"> <li :class="{ now: now === 4 }" class="item item-link" @click="$router.push(`?${query}=4`)" > <span class="text"> 4 </span> </li> </template> </template> </template> // ?でなければ(=最大ページが5より大きい6〜) <template v-else> // ?最大ページ6〜かつ現在いるページが4より少ない(=1, 2, 3の時) <template v-if="now < 4"> <li :class="{ now: now === 2 }" class="item item-link" @click="$router.push(`?${query}=2`)" > <span class="text"> 2 </span> </li> <li :class="{ now: now === 3 }" class="item item-link" @click="$router.push(`?${query}=3`)" > <span class="text"> 3 </span> </li> <li // ?現在いるページが4より少ないかつ、3ページ目にいる時 v-if="now === 3" class="item item-link" @click="$router.push(`?${query}=4`)" > <span class="text"> 4 </span> </li> <li class="item item-dots"> <div class="dot" /> <div class="dot" /> <div class="dot" /> </li> </template> // ?最大ページ6〜かつ現在いるページが1, 2, 3でなく4で〜 現在いるページに2を出しても最大ページ数と同じか少なければ (4ページ目にいるなら4 + 2、最大ページ7の方が大きいためfalse) (6ページ目にいるなら6 + 2、最大ページ7より大きいためtrue) <template v-else-if="length <= now + 2"> <li class="item item-dots"> <div class="dot" /> <div class="dot" /> <div class="dot" /> </li> // ?最大ページ数から2を引いた数字が現在いるページだったら 最大ページ数から3を引いたページ数を表示させる (5ページ目にいるなら7-2 =5でtrue、7-3 =4が表示される) <li v-if="now === length - 2" class="item item-link" @click="$router.push(`?${query}=${length - 3}`)" > <span class="text"> {{ length - 3 }} </span> </li> <li :class="{ now: now === length - 2 }" class="item item-link" @click="$router.push(`?${query}=${length - 2}`)" > <span class="text"> {{ length - 2 }} </span> </li> <li :class="{ now: now === length - 1 }" class="item item-link" @click="$router.push(`?${query}=${length - 1}`)" > <span class="text"> {{ length - 1 }} </span> </li> </template> // ?最大ページ6〜かつ、今までのパターンに該当しない (上の?のfalse、現在4ページの場合) <template v-else> <li class="item item-dots"> <div class="dot" /> <div class="dot" /> <div class="dot" /> </li> <li class="item item-link" @click="$router.push(`?${query}=${now - 1}`)" > <span class="text"> {{ now - 1 }} </span> </li> <li class="item item-link now"> <span class="text"> {{ now }} </span> </li> <li class="item item-link" @click="$router.push(`?${query}=${now + 1}`)" > <span class="text"> {{ now + 1 }} </span> </li> <li class="item item-dots"> <div class="dot" /> <div class="dot" /> <div class="dot" /> </li> </template> </template> </template> <li :class="{ now: now === length }" class="item item-link" @click="$router.push(`?${query}=${length}`)" > <span class="text"> {{ length }} </span> </li> </ul> <button class="btn btn-next" @click="$router.push(`?${query}=${now + 1 <= length ? now + 1 : length}`)" > 進む </button> </div> </template> <script> export default { props: { query: { type: String, required: true, }, length: { type: Number, required: true, }, now: { type: Number, required: true, }, }, } </script>これで完成です?
【最大ページ2は?】
if は最大ページ3以上で分岐。
2はどうなっているかというと…
ul 内の構造を黄色い枠で分けています?
・1固定表示
・3ページ以上で分岐
・最大ページ固定表示Step4: コンテンツページでpropsにdataを渡す
【sample.vue】
Pagination.vue<template> <div class="page"> <Pagination query="members" :length="7" :now="Number($route.query.members) || 1" class="nav" /> </div> </template> <script> import Pagination from '~/components/Pagination.vue' export default { components: { Pagination, }, data() { return { members: [ { name: aLiz }, ], } }, } </script> <style lang="scss" scoped> </style>このアカウントでは
Nuxt.js、Vue.jsを誰でも分かるよう、
超簡単に解説しています??
これからも発信していくので、
ぜひフォローしてください♪
https://twitter.com/aLizlab
- 投稿日:2020-01-21T18:25:32+09:00
最近の要素・ノード操作型メソッド事情
はじめに
JavaScript もどんどん進化し、ライブラリ等を使わずにネイティブな記述だけでもいろんな処理が書けるようになってきています。
実際に使うかどうかはおいておき、今こんなのがあるよというやつです。※以下、jQuery を使わない書き方を「ネイティブ」と表現しています
jQuery っぽいメソッド
もう最近では jQuery を使わないことの方が多い気がします。
なんか気づいたら jQuery で使っていたメソッドのようなものがネイティブで書けるようになってます。
.closest()
.closest()
といえば、直近の祖先の要素を取得するやつです。
ネイティブではElement.closest()
として実装されています。HTML<div class="target"> <div class="target"> <div> <div id="grandchild"></div> </div> </div> </div>jQueryvar target = $('#grandchild').closest('.target'); target.prop({ id: 'grandparent' });JavaScriptconst target = document.getElementById('grandchild').closest('.target'); target.id = 'grandparent';これは両方とも以下になります。
HTML<div class="target"> <div class="target" id="grandparent"> <div> <div id="grandchild"></div> </div> </div> </div>
.prepend()
と.append()
.prepend()
と.append()
といえば、前者が指定した要素の最後に要素を挿入、後者が指定した要素の最初に要素を挿入するやつです。
ネイティブでそれぞれParentNode.prepend()
とParentNode.append()
というメソッドとして実装されています。
Node.appendChild()
とParentNode.append()
の違いは返り値です。
返り値については、前者は挿入される要素・ノードなのに対して、後者はundefined
です。HTML<div id="target"> <p>ABCDEFG</p> </div>jQueryconst target = $('#target'); // .prepend() const span = $('<span>'); target.prepend(span); // .append() const div = $('<div>'); target.append(div);JavaScriptconst target = document.getElementById('target'); // .prepend() const span = document.createElement('span'); target.prepend(span); // .append() const div = document.createElement('div'); target.append(div);これは両方とも以下になります。
HTML<div id="target"> <span></span> <p>ABCDEFG</p> <div></div> </div>引数で複数指定が可能
.prepend()
と.append()
は引数を増やすことで複数の要素・ノードを挿入できます。HTML<div id="target"> <p>ABCDEFG</p> </div>JavaScriptconst target = document.getElementById('target'); const span01 = document.createElement('span'); const div01 = document.createElement('div'); target.prepend(span01, div01, '12345'); const span02 = document.createElement('span'); const div02 = document.createElement('div'); target.append(span02, div02, '67890');結果は
HTML<div id="target"> <span></span> <div></div> 12345 <p>ABCDEFG</p> <span></span> <div></div> 67890 </div>ちょっと気をつけたいのが
.prepend()
ですね。引数で複数指定した場合は、引数の順番のまま挿入されます。テキストの場合の挙動の違い
一見すると同じように見えますが、テキストの場合は jQuery とネイティブで違いが出てきます。
.append()
で例を見ます。HTML<div id="test"> <p>ABCDEFG</p> </div>JavaScriptconst list = document.getElementById('test'); // jQuery $(list).append('<p>jQuery</p>'); // native list.append('<p>native</p>');結果は……
HTML<div id="test"> <p>ABCDEFG</p> <p>jQuery</p> <p>native</p> </div>jQuery ではHTML文字列として扱われ、ネイティブではテキストノードとして扱われます。
ネイティブでHTML文字列として挿入したい場合は
element.insertAdjacentHTML
で代用できます。JavaScriptconst list = document.getElementById('test'); list.insertAdjacentHTML('beforeend', '<p>native</p>');仕様
ParentNode.append() - Web API | MDN
ParentNode.prepend() - Web API | MDN
.before()
と.after()
.before()
と.after()
といえば、前者が指定した要素の前に要素を挿入、後者が指定した要素の後ろに要素を挿入するやつです。
ネイティブでそれぞれChildNode.before()
とChildNode.after()
というメソッドとして実装されています。HTML<p id="target">ABCDEFG</p>jQueryconst target = $('#target'); // .before() const span = $('<span>'); target.before(span); // .after() const div = $('<div>'); target.after(div);JavaScriptconst target = document.getElementById('target'); // .before() const span = document.createElement('span'); target.before(span); // .after() const div = document.createElement('div'); target.after(div);これは両方とも以下になります。
HTML<span></span> <p id="target">ABCDEFG</p> <div></div>引数で複数指定が可能
.before()
と.after()
は引数を増やすことで複数の要素・ノードを挿入できます。HTML<p id="target">ABCDEFG</p>JavaScriptconst target = document.getElementById('target'); const span01 = document.createElement('span'); const div01 = document.createElement('div'); target.before(span01, div01, '12345'); const span02 = document.createElement('span'); const div02 = document.createElement('div'); target.after(span02, div02, '67890');結果は
HTML<span></span> <div></div> 12345 <p id="target">ABCDEFG</p> <span></span> <div></div> 67890テキストの場合の挙動の違い
.prepend()
と.append()
の時と同じように、テキストの場合は jQuery とネイティブで違いが出てきます。HTML<p id="target">ABCDEFG</p>JavaScriptconst target = document.getElementById('target'); // jQuery $(target).after('<p>jQuery</p>'); // native target.after('<p>native</p>');結果は……
HTML<p id="target">ABCDEFG</p> <p>jQuery</p> <p>native</p>jQuery ではHTML文字列として扱われ、ネイティブではテキストノードとして扱われます。
ネイティブでHTML文字列として挿入したい場合は
element.insertAdjacentHTML
で代用できます。JavaScriptconst target = document.getElementById('target'); target.insertAdjacentHTML('afterend', '<p>native</p>');仕様
ChildNode.before() - Web API | MDN
ChildNode.after() - Web API | MDN
.remove()
.remove()
といえば、DOMツリーから要素を取り除く(削除ではない)メソッドです。
ネイティブではChildNode.remove()
として実装されています。
Node.removeChild()
との違いは、指定した要素がremoveChild
を実行する要素内に存在している必要があります。
要するに、取り除く要素の親要素から実行してあげる必要があります。自分自身を取り除くメソッドではありません。
ChildNode.remove()
は、自分自身を取り除くメソッドです。HTML<p id="target">ABCDEFG</p> <p>HIJKLMN</p>jQuery$('#target').remove();JavaScriptdocument.getElementById('target').remove();これは両方とも以下になります。
HTML<p>HIJKLMN</p>仕様
ChildNode.remove() - Web APIs | MDN
.replaceWith()
.replaceWith()
は要素を置換するメソッドで、シンプルな使い方では jQuery とネイティブでは動作的にほぼ同じです。
ChildNode.replaceWith()
は jQuery のように引数に関数を使えません。HTML<p id="target">ABCDEFG</p>jQueryconst div = $('<div>'); $('#target').replaceWith(div);JavaScriptconst div = document.createElement('div'); document.getElementById('target').replaceWith(div);これは両方とも以下になります。
HTML<div></div>
テキストの場合の挙動の違い
ここまでくればおわかりかと思いますが、他のメソッド同様に挙動が異なります。
HTML<p id="target01">ABCDEFG</p> <p id="target02">HIJKLMN</p>JavaScript// jQuery $('#target01').replaceWith('<p>jQuery</p>'); // native document.getElementById('target02').replaceWith('<p>native</p>');結果は……
HTML<p>jQuery</p> <p>native</p>jQuery ではHTML文字列として扱われ、ネイティブではテキストノードとして扱われます。
ネイティブでHTML文字列として挿入したい場合は
Element.outerHTML
で代用しようと思えばいけます。JavaScriptconst target02 = document.getElementById('target02'); target02.outerHTML = '<p>native</p>';仕様
ChildNode.replaceWith() - Web APIs | MDN
思ったこと
相変わらずDOM系のメソッドは似たようなものがたくさんあったりしてよくわからんですね……
- 投稿日:2020-01-21T14:19:18+09:00
文章読み上げアプリをJavaScriptで作成してみました
はじめに
最近文章読み上げアプリをJavaScriptで作成したので作成するうえで苦労した点などをまとめてみます。
アプリはHerokuにアップしてあるので、リンクへ飛んでいただければ試していただけます。
読みあげ君へのリンクはこちら
どうして"読みあげ君”を作ろうと思ったかというと、”初音ミク”に似たソフトで誰の声でも当てられたら面白いな、と感じたことがキッカケです。読みあげ君の画像↓
苦労した点について
1.テキストエリアへ打ち込まれた文字と音声を対応させる方法
2.音声を連続で再生する方法1.テキストエリアへ打ち込まれた文字と音声を対応させる方法
この解決策はとても単純で、連想配列を使用しました。
var voiceDictionary={1:'あ',2:'い',............71:'ぽ'};
といった風に1文字ずつキー値と対応させる方法です。
詳しいコードはリンク先で見ていただければ、と思いますが例えば、"あほか"と打ち込まれたとすると
あ→1、ほ→30、か→6といった風に連想配列を用いて数値へと変換します。
あとは、voiceDictionaryの音表通りに声のmp3ファイルを個別に配置していくことで、文字と音声の対応が出来ました。2.音声を連続で再生する方法
最初は、for文で(打ち込まれた文字数).lengthの回数を連続で鳴らそうかと思ったのですが、何度やってもfor文ではうまくいかなかったので
(オーディオインスタンスの変数名).addEventListener('ended',function(){
//(オーディオインスタンス).play();が終了した後動作させたいコードを入れる
}
という構文を追加することで解決できました。
この構文の前に、
(オーディオインスタンス).play();
というコードがあるのですが、そのplay();が終了したことを検知して、動作する構文となっています。そして、その構文の中に音声を連続で再生するためのアルゴリズムを入れたら上手く解決できました。
終わりに
つたない説明で申し訳ありません。
Qiitaへの記事を書きながら、自分の説明力不足を痛感しました・・・。
- 投稿日:2020-01-21T12:08:59+09:00
花粉症LINE BotからのデータをWEBカレンダーに表示する(花粉カレンダー作成④)
概要
耳鼻咽喉科の開業医をしています。
花粉症の患者さんに使ってもらえるような花粉飛散情報が分かるカレンダーアプリを作りたいと思っています。
これまでカレンダーを表示して予定を入れることと、ユーザー認証の実装、LINEのデータをFirebaseに貯めるところまで行ってきました。
Vue.js×FullCallendarでWEBカレンダー作成(花粉カレンダー作成①)
Auth0で簡単にユーザー認証を実装(花粉カレンダー作成②)
花粉症LINE Botのデータをnode.jsを使ってFirebaseに出し入れする(花粉カレンダー作成③)今回はLINEBotのデータが記録されているFirebaseのdatabaseのデータをカレンダーに表示することに挑戦しました。
LINEBotの記事はこちら
花粉症の重症度を判定し自分に合う市販薬を教えてくれるLINE Botの作成完成動画
作成
1.FirebaseのRealtime Databaseの確認
LINEのデータはFirebaseのRealtime Databaseに記録されています。
データは以下のように収納されています。今回は以下の情報を取得して重症度や薬剤名、緯度経度をリアルタイムでカレンダーに記入していきたいと思います。
・postback.data(花粉症の重症度や使用している薬剤の情報)
・postback.params.datatime(重症度判定を行った日や薬剤使用開始した日の情報)
・sorce.userID(LINEのユーザーID)
個別の花粉飛散情報を表示するため
・message.latitude(ユーザー位置情報 緯度)
・message.latitude(ユーザー位置情報 経度)データは.(ドット)で深堀していくことができるようです。
2.実装
以前作成したCalendar.vueに追記していきます。
Vue.js×FullCallendarでWEBカレンダー作成(花粉カレンダー作成①)methods: { }の中に以下を追記します。
緯度や経度は本当はデータが取得できるだけでいいのですが、今回は本日の日付で表示してみました。childAdded(snap) { const message = snap.val(); const mes = message.events[0]; if (mes.type == "postback") { console.log(mes.postback.data); console.log(mes.postback.params.datetime); console.log(mes.source.userId); this.calendarEvents.push({ title: mes.postback.data,//重症度や薬剤 start: mes.postback.params.datetime, end: mes.postback.params.datetime }); } if (mes.type == "message") { if(mes.message.type=="location"){ console.log(mes.message.latitude); console.log(mes.message.longitude); userlat = mes.message.latitude;// 緯度 userlong = mes.message.longitude;//経度 }; this.calendarEvents.push({ // title: mes.message.text, title: `緯度${userlat}`, start: "2020-01-19T09:00:00", end: "2020-01-19T10:30:00" }, { title:`緯度${userlong}`, start: "2020-01-19T09:00:00", end: "2020-01-19T10:30:00" } ); } },async created() { }の中に以下を追記して完成です。
const ref_message = firebase.database().ref("protoout/studio/messageList"); //新しいメッセージ2件だけ表示する ref_message.limitToLast(2).on("child_added", this.childAdded);考察
Firebaseのデータをカレンダーに表示することが出来ました。
次は気象APIから花粉情報を表示できるようにしたいと思います。
- 投稿日:2020-01-21T12:05:57+09:00
Zeitの最強ホスティングサービスnowのDNS設定にレコードを追加する方法
今回始めてNext.jsアプリをnowにデプロイしました。
nowでは簡単に独自ドメイン設定ができるので、お名前どっとこむで取得したものを設定しました。
また、SEO対策で欠かせない?Google Search Consoleでのドメイン所有権確認のためにTXTレコードの追加が必要になり、ここで少しつまずいたのでメモ的にDNSレコード追加方法を書いておきます。ドメイン設定はName Server転送を選択
そもそものドメイン設定は、Zeitがおすすめしているネームサーバー転送で行いました。
つまり、これから自分のドメインにTXT等レコードを追加したい場合は、お名前ドットコムの設定ではなく、nowのものをイジる必要があるわけです。nowで設定した独自ドメインにTXTレコードを追加してみる
nowは非常にミニマルで美しい管理画面を提供してくれているのですが、今回やりたい、レコードの追加はブラウザではできないようです。ではどうやるのか、コマンドラインツールnowコマンドです。
nowコマンドでTXTレコード追加
npmやyarnでnowコマンドをグローバルインストールした後、
$ npm i -g now # Or yarn global add nownowにログインします
$ now login
そして以下コマンドで一発完了
$ now dns add sample.com @ TXT "TXTレコードの値"宣伝
こんにちは。
自分は新卒でヤフー→4年で退職→2019/05よりバンクーバー在住のソフトウェアデベロッパーです。
Node.js/Vue/Nuxt/React/Next 周りならフロントバックエンド共に開発できます。
バンクーバーからのリモートでもOK!という案件お待ちしております!
@taishikat0_Ja
taishikato.com/resume
- 投稿日:2020-01-21T12:02:13+09:00
Javascriptのシンプルな構成でAWS Cognitoを理解する
概要
いろいろと理解しなければならないことが多いですが、まずは、できるだけシンプルな構成でAWS Cognitoの基礎を理解します。
jQueryは使わずにJavascriptのみです。
ただし、アカウントの属性には標準属性とカスタム属性を設定します。
今回説明する内容を踏まえて、実用ではAmplifyを使うと良いかと思っています。画面遷移
まずはサインアップ画面
サインアップするとメールで検証リンクが配信されます。
Verify Email
リンクをクリックすると検証が完了します。
次にサインイン画面
最後に認証されたアカウントしか入ることのできない画面
Sign Out
をクリックするとサインアウトしてサインイン画面に遷移します。ファイル構成
jsフォルダへ準備するライブラリ
それぞれのREAD.MEをしっかり確認してください。
AWS Cognitoの設定
- AWSへサインイン
- Cognitoのコンソールへ
- リージョンを東京へ変更
ユーザープールの管理
をクリックユーザープールを作成する
をクリック
プール名
へお好きな名前を付けてください。
デフォルトを確認する
をクリックすると↓の画面になります。
- サイドバーの
属性
をクリック- 今回は↓のように設定します。
カスタム属性にはrole
と入力しますが、自動でcustom:role
と変更されます。次のステップ
をクリック- 「ポリシー」の設定
- 「MFAそして確認」の設定
- 「メッセージのカスタマイズ」の設定
- 「アプリクライアント」の設定で
アプリクライアントの追加
をクリック
- アプリクライアント名を入力し、
クライアントシークレットを作成
のチェックを外す
- サイドバーで「確認」をクリックすると↓が表示され、
プールの作成
をクリック
- プールIDが表示されるので、それをメモ
- サイドバーから「アプリクライアント」を選択すると
アプリクライアントID
が表示されるので、それをメモ
- サイドバーから「ドメイン名」を選択するとAmazon Cognito ドメインの作成ができるので好きな名前を入力してドメインを作成します。
- これでユーザープールの設定が完了
フェデレーティッドアイデンティティの設定
- Cognitoコンソールの上部にある
フェデレーティッドアイデンティティ
をクリック
- をクリック
- 使用開始ウィザードでIDプール名に好きな名前を入力
認証されていないIDに対してアクセスを有効にする
をチェック- 認証プロバイダーを展開し、Cognitoを選択
- メモしたユーザープールIDとアプリクライアントIDを入力
- プールの作成をクリック
- 次の画面ではデフォルトの内容を確認して、
許可
をクリック- サイドバーでサンプルコードを選択し、プラットフォームを
Javascript
を選択。AWS認証情報の取得に表示されているコードをメモ
- これでCognitoの設定が完了
サインアップページの作成
signup.html<!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <title>Sign Up</title> <script src="./js/jsbn.js"></script> <script src="./js/jsbn2.js"></script> <script src="./js/sjcl.js"></script> <script src="./js/aws-sdk.min.js"></script> <script src="./js/aws-cognito-sdk.min.js"></script> <script src="./js/amazon-cognito-identity.min.js"></script> </head> <body> <div id="signup"> <h1>Sign Up</h1> <div id="message"><span id="message-span" style="color: red;"></span></div> <form name="form-signup"> <span style="display: inline-block; width: 150px;">User ID(Email)</span> <input type="text" id="email" placeholder="Email Address" /> <br /> <span style="display: inline-block; width: 150px;">Name</span> <input type="text" id="name" placeholder="Name" /> <br /> <span style="display: inline-block; width: 150px;">Password</span> <input type="password" id="password" placeholder="Password" /> <br /><br /> <input type="button" id="createAccount" value="Create Account" /> </form> </div> <br /> <a href="./signin.html">Sign In!</a> <script src="./js/signup.js" defer></script> </body> </html>サインアップの処理
これまでにメモした
ユーザープールID
、クライアントID
、AWS認証情報
を張り付けます。js/signup.js(() => { // ユーザープールの設定 const poolData = { UserPoolId: "us-east-1_xxxxxxxx", ClientId: "xxxxxxxxxxxxxxxxxxxxx" }; const userPool = new AmazonCognitoIdentity.CognitoUserPool(poolData); const attributeList = []; // Amazon Cognito 認証情報プロバイダーを初期化します AWS.config.region = "us-east-1"; // リージョン AWS.config.credentials = new AWS.CognitoIdentityCredentials({ IdentityPoolId: "us-east-1:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }); // 「Create Account」ボタン押下時 const createAccountBtn = document.getElementById("createAccount"); createAccountBtn.addEventListener("click", () => { /** * サインアップ処理。 */ const username = document.getElementById("email").value; const name = document.getElementById("name").value; const password = document.getElementById('password').value; // 何か1つでも未入力の項目がある場合、処理終了 const message = document.getElementById("message-span"); if (!username | !name | !password) { message.innerHTML = "未入力項目があります。"; return false; } // ユーザ属性リストの生成 const dataName = { Name: "name", Value: name }; const dataRole = { Name: "custom:role", Value: "5" }; const attributeName = new AmazonCognitoIdentity.CognitoUserAttribute( dataName ); const attributeRole = new AmazonCognitoIdentity.CognitoUserAttribute( dataRole ); attributeList.push(attributeName); attributeList.push(attributeRole); // サインアップ処理 userPool.signUp(username, password, attributeList, null, (err, result) => { if (err) { message.innerHTML = err.message; return; } else { // サインアップ成功の場合、アクティベーション画面に遷移する alert( "登録したメールアドレスへアクティベーション用のリンクを送付しました。" ); location.href = "signin.html"; } }); }); })();サインインページの作成
signin.html<!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <title>Sign In</title> <script src="./js/jsbn.js"></script> <script src="./js/jsbn2.js"></script> <script src="./js/sjcl.js"></script> <script src="./js/aws-sdk.min.js"></script> <script src="./js/aws-cognito-sdk.min.js"></script> <script src="./js/amazon-cognito-identity.min.js"></script> </head> <body> <div id="signin"> <h1>Sign In</h1> <div id="message"><span id="message-span" style="color: red;"></span></div> <form name="form-signin"> <span style="display: inline-block; width: 150px;">Email Address</span> <input type="text" id="email" placeholder="Email Address" /> <br /> <span style="display: inline-block; width: 150px;">Password</span> <input type="password" id="password" placeholder="Password" /> <br /><br /> <input type="button" id="signinButton" value="Sign In" /> </form> </div> <br /> <a href="./signup.html">Sign Up!</a> <script src="./js/signin.js" defer></script> </body> </html>サインインの処理
これまでにメモした
ユーザープールID
、クライアントID
、AWS認証情報
を張り付けます。js/signin.js(() => { // ユーザープールの設定 const poolData = { UserPoolId: "us-east-1_xxxxxxxx", ClientId: "xxxxxxxxxxxxxxxxxxxxxxxxx" }; const userPool = new AmazonCognitoIdentity.CognitoUserPool(poolData); // Amazon Cognito 認証情報プロバイダーを初期化します AWS.config.region = "us-east-1"; // リージョン AWS.config.credentials = new AWS.CognitoIdentityCredentials({ IdentityPoolId: "us-east-1:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }); /** * サインイン処理 */ document.getElementById("signinButton").addEventListener("click", () => { const email = document.getElementById('email').value; const password = document.getElementById('password').value; // 何か1つでも未入力の項目がある場合、メッセージを表示して処理を中断 const message = document.getElementById('message-span'); if (!email | !password) { message.innerHTML = "All fields are required."; return false; } // 認証データの作成 const authenticationData = { Username: email, Password: password }; const authenticationDetails = new AmazonCognitoIdentity.AuthenticationDetails( authenticationData ); const userData = { Username: email, Pool: userPool }; const cognitoUser = new AmazonCognitoIdentity.CognitoUser(userData); // 認証処理 cognitoUser.authenticateUser(authenticationDetails, { onSuccess: result => { const idToken = result.getIdToken().getJwtToken(); // IDトークン const accessToken = result.getAccessToken().getJwtToken(); // アクセストークン const refreshToken = result.getRefreshToken().getToken(); // 更新トークン console.log("idToken : " + idToken); console.log("accessToken : " + accessToken); console.log("refreshToken : " + refreshToken); // サインイン成功の場合、次の画面へ遷移 location.href = "index.html"; }, onFailure: err => { // サインイン失敗の場合、エラーメッセージを画面に表示 console.log(err); message.innerHTML = err.message; } }); }); })();認証完了で閲覧できるページの作成
index.html<!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <title>Menu</title> <script src="./js/jsbn.js"></script> <script src="./js/jsbn2.js"></script> <script src="./js/sjcl.js"></script> <script src="./js/aws-sdk.min.js"></script> <script src="./js/aws-cognito-sdk.min.js"></script> <script src="./js/amazon-cognito-identity.min.js"></script> </head> <body> <div id="menu"> <h1 id="name"></h1> <h2 id="role"></h2> <p id="email"></p> </div> <button id="signout" hidden>Sign Out</button> <script src="./js/index.js" defer></script> </body> </html>認証済みかチェックする処理
これまでにメモした
ユーザープールID
、クライアントID
、AWS認証情報
を張り付けます。js/index.js(() => { // ユーザープールの設定 const poolData = { UserPoolId: "us-east-1_xxxxxxxxxx", ClientId: "xxxxxxxxxxxxxxxxxxxxxxxxxx" }; const userPool = new AmazonCognitoIdentity.CognitoUserPool(poolData); const cognitoUser = userPool.getCurrentUser(); // 現在のユーザー const currentUserData = {}; // ユーザーの属性情報 // Amazon Cognito 認証情報プロバイダーを初期化します AWS.config.region = "us-east-1"; // リージョン AWS.config.credentials = new AWS.CognitoIdentityCredentials({ IdentityPoolId: "us-east-1:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }); // 現在のユーザーの属性情報を取得・表示する // 現在のユーザー情報が取得できているか? if (cognitoUser != null) { cognitoUser.getSession((err, session) => { if (err) { console.log(err); location.href = "signin.html"; } else { // ユーザの属性を取得 cognitoUser.getUserAttributes((err, result) => { if (err) { location.href = "signin.html"; } // 取得した属性情報を連想配列に格納 for (i = 0; i < result.length; i++) { currentUserData[result[i].getName()] = result[i].getValue(); } document.getElementById("name").innerHTML = "ようこそ!" + currentUserData["name"] + "さん"; document.getElementById("role").innerHTML = "Your Role is " + currentUserData["custom:role"]; document.getElementById("email").innerHTML = "Your E-Mail is " + currentUserData["email"]; // サインアウト処理 const signoutButton = document.getElementById("signout"); signoutButton.addEventListener("click", event => { cognitoUser.signOut(); location.reload(); }); signoutButton.hidden = false; console.log(currentUserData); }); } }); } else { location.href = "signin.html"; } })();動作のテスト
一連の流れをブラウザでテスト
- サインアップ
- メールでアクティベート
- サインイン
- 認証情報の表示
- サインアウト
Cognitoのコンソールでユーザーの状況が確認できる
ユーザーを無効化したり、削除したり、詳細な操作ができる
最後に
他にもパスワードの変更や再設定など必要な機能があるかと思いますが、入門としてはシンプルに作成できたかと思います。
ここからJWTや認証・認可などへ広げていけば良いかと思います。
- 投稿日:2020-01-21T11:57:40+09:00
鼓膜の画像を送り質問に返答すれば、自動で中耳炎の診断や治療方針が返されるLINE Botを作成(ヒーローズ・リーグ2019 LINEテーマ賞)
概要
耳鼻咽喉科の開業医をしています。
以前、質問に答えていくと急性中耳炎の重症度が分かるLINE Botと
鼓膜画像を送ると正常か中耳炎かを答えてくれるLINE Botを作成しました。急性中耳炎の重症度が分かるLINE Botの作成
Microsoft Custom Vision Serviceによる中耳炎画像認識LINE Botの作成今回、二つのBotを組み合わせて、鼓膜の画像を送り質問に返答すれば、自動で中耳炎の診断や治療方針が返されるLINE Botを作成しました。
概念図
完成動画/画像
— 病気のセルフチェック (@Selfcheckhealt1) January 21, 2020作成
以前の作成したBotのコードを変えていきます。
Azure Custom Vision ServicesのPrediction APIの発行の仕方もこちらの記事を参考にして下さい。Microsoft Custom Vision Serviceによる中耳炎画像認識LINE Botの作成
まず、ユーザーから送られてくるのがメッセージか画像かで処理を分けます。
function handleEvent(req, res) { if (req.body.events[0].type === 'message' && req.body.events[0].message.type === 'text') { return handleTextEvent(req.body.events[0]); }else if(req.body.events[0].message.type === 'image'){ return handleImageEvent(req.body.events[0]); } console.log("サポートされていないメッセージです"); }鼓膜画像が送られてきたときの処理です。
最も確率が高い診断名とその確率が表示されます。
診断が急性中耳炎の場合は重症度判定に必要な「鼓膜の発赤」「鼓膜の腫脹」「耳漏」の程度を確率で表示し重症度スコアを計算します。
その後年齢に関する質問が開始され、クイックリプライで表示されます。function handleImageEvent(event) { console.log("画像が来たよ"); // ユーザーがLINE Bot宛てに送った写真のURLを取得する const options = { url: `https://api.line.me/v2/bot/message/${event.message.id}/content`, method: 'get', headers: { 'Authorization': 'Bearer 自分のchannelAccessToken' , }, encoding: null }; Request(options, function(error, response, body) { if (!error && response.statusCode == 200) { //保存 console.log(options.url + '/image.jpg'); let strURL = options.url + '/image.jpg'; //Nowでデプロイする場合は、/tmp/のパスが重要 fs.writeFileSync(`/tmp/` + event.message.id + `.png`, new Buffer(body), 'binary'); const filePath = `/tmp/` + event.message.id + `.png`; //Azure Custom Vision APIの設定 const config = { "predictionEndpoint": "ひかえておいたURL", "predictionKey": 'ひかえておいたKey' }; let result1; cv.sendImage( filePath, config, (data) => { console.log(data); let result0=""; // let result1; let result2 = ""; let result3 = ""; let result4 = ""; let result5 = ""; let strName = ""; let Probability ; let strProbability; for (var i = 0; i <4; i++) { strName = data.predictions[i].tagName; Probability = data.predictions[i].probability * 100; strProbability = Probability.toFixed(); if (strName == "急性中耳炎") { result1 = "急性中耳炎"; result0 = "ですね。\n確率は"+strProbability + '%\n\n'; }else if (strName == "滲出性中耳炎") { result1 = "滲出性中耳炎"; result0 = strProbability + '%'; }else if(strName == "正常鼓膜") { result1 = "正常鼓膜"; result0 = strProbability + '%'; } } let symptoms = {}; let score = 0; if (result1 == "急性中耳炎") { for (var i = 0; i < 10; i++) { strName = data.predictions[i].tagName; Probability = data.predictions[i].probability * 100; strProbability = Probability.toFixed(); if (symptoms["発赤"] === undefined) { if (strName == "発赤:なし") { symptoms["発赤"] = "発赤なし" + strProbability + '%,\n'; //score0 } else if (strName == "発赤:一部") { symptoms["発赤"] = "発赤一部" + strProbability + '%,\n'; score += 2; } else if (strName == "発赤:全体") { symptoms["発赤"] = "発赤全体" + strProbability + '%,\n'; score += 4; } result2 = symptoms["発赤"]; } if (symptoms["腫脹"] === undefined) { if (strName == "腫脹:なし") { symptoms["腫脹"] = "腫脹なし" + strProbability + '%,\n'; } else if (strName == "腫脹:一部") { symptoms["腫脹"] = "腫脹一部" + strProbability + '%,\n'; score += 4; } else if (strName == "腫脹:全体") { symptoms["腫脹"] = "腫脹全体" + strProbability + '%,\n'; score += 8; } result3 = symptoms["腫脹"]; } if (symptoms["耳漏"] === undefined) { if (strName == "耳漏:なし") { symptoms["耳漏"] = "耳漏なし" + strProbability + '%,\n'; } else if (strName == "耳漏:あり") { symptoms["耳漏"] = "耳漏あり" + strProbability + '%,\n'; score += 2; } result4 = symptoms["耳漏"]; } } // } client.replyMessage(event.replyToken, { "type": "text", // ① "text": result1 + result0 + result2 + result3 + result4 + "➡重症度スコア:" + String(score)+"\n\nいくつか質問にお答えください。\n\n2歳未満ですか?", "quickReply": { "items": [ { "type": "action", "action": { "type": "message", "label": "いいえ", "text": "2歳以上 トータルスコア:" + String(score) } }, { "type": "action", "action": { "type": "message", "label": "はい", "text": "2歳未満 トータルスコア:" + String(score + 3) } } ] } }); } else if (result1 == "滲出性中耳炎") { client.replyMessage(event.replyToken, { type: 'text', text: result1 + "ですね。\n確率は" + result0 , }); } else if (result1 == "正常鼓膜") { client.replyMessage(event.replyToken, { type: 'text', text: result1 + "ですね。\n確率は" + result0 , }); } try { fs.unlinkSync(filePath); return true; } catch(err) { return false; } return; }, (error) => { console.log(error) } ); } else { console.log('imageget-err'); } }); }メッセージに対する処理は、function handleTextEvent(event) { }の中に
急性中耳炎の重症度が分かるLINE Botの作成のLINE botのプログラムを入れて追記すると完成です。質問に対するクイックリプライの回答から重症度スコアを加算していき、すべての質問が終わるとトータルスコアから急性中耳炎の重症度を判定し、ガイドラインで推奨されている治療を返します。
考察
鼓膜の画像さえきれいに撮影できれば、高い精度で急性中耳炎のガイドラインに沿った診断と推奨治療を返すBotを作成できました。
こちらのBotで昨年末に開催された開発コンテストのヒーローズ・リーグ2019で賞(LINEテーマ賞 by LINE株式会社様)をいただきとても嬉しかったです。
また、先日耳鼻咽喉科の学術講演会でこのBotについても発表させていただき耳鼻咽喉科の先生方からもかなり反響がありました。
鼓膜の撮影をするカメラ(デジタル耳鏡)は通販で3~4000円で購入できるため、一般の方が自宅で撮影することもできるのですが、Botが病気の診断することは現在の法律で禁じられているため、こちら公開して使って頂くことは出来ません。データを増やし精度を上げながら自院で医師の指導のもと中耳炎の再来患者さんを中心に使用していただいて、有効性や安全性を検証していきたいと思っています。
- 投稿日:2020-01-21T10:49:46+09:00
【初心者】JavaScriptで「コマ移動ゲーム(横移動のみ)」を作ってみた
最近、JavaScriptを勉強しています。
練習として、「コマ移動ゲーム(横移動のみ)」を作ってみました。
簡単なものですが、アウトプットが大切だと考えています。笑HTMLを記述する
index.html<h1>コマ移動ゲーム(横移動のみ)</h1> <p id="field"></p> <input type="button" value="左" id="btn_left"> <input type="button" value="右" id="btn_right"> <input type="button" value="リセット" id="btn_reset"> <script src="main.js"></script>タイトル、コマが移動するフィールド、3つのボタンという構成です。
フィールドの描画は、JavaScriptにお任せしているので、要素の中身は空です。
JavaScriptの読み込みも忘れずにしています。JavaScriptを記述する
main.jsconst field = document.getElementById('field'); const btn_left = document.getElementById('btn_left'); const btn_right = document.getElementById('btn_right'); const btn_reset = document.getElementById('btn_reset'); const loopCount = 10; let position = 0; const fieldReset = (reset) => { if (reset) { position = 0; } field.textContent = ''; }; const draw = () => { for (let i = 0; i < loopCount; i++) { if (position === i) { field.textContent += '●'; } else { field.textContent += '〇'; } } }; draw(); btn_left.addEventListener('click', () => { fieldReset(); if (position > 0) { position--; } draw(); }); btn_right.addEventListener('click', () => { fieldReset(); if (position < loopCount - 1) { position++; } draw(); }); btn_reset.addEventListener('click', () => { fieldReset('reset'); draw(); });最初にフィールドとボタンのHTML要素を取得し、
フィールドの数とコマの位置を示す値を定義しています。
loopCount
はフィールドの数(動ける範囲)なのですが、
ちょっと定数名が分かりにくいですね、反省します。draw() について
const draw = () => { for (let i = 0; i < loopCount; i++) { if (position === i) { field.textContent += '●'; } else { field.textContent += '〇'; } } };関数
draw
は、フィールドを描画してくれます。
コマの位置のみ●
で、その他は〇
で表現しています。
フィールドのHTML要素内の文字列を足していく、という形で実現しています。fieldReset(reset) について
const fieldReset = (reset) => { if (reset) { position = 0; } field.textContent = ''; };関数
fieldReset(reset)
は、フィールドをリセットしてくれます。
引数に何かしら与えられた場合のみ、コマの位置もリセットしてくれます。
フィールドのHTML要素内の文字列を空にする、という形で実現しています。3つのボタンについて
それぞれのボタンが押されたときの挙動を説明します。
- 「左」:フィールドをリセットし、変数
position
を-1
して再描画- 「右」:フィールドをリセットし、変数
position
を+1
して再描画- 「リセット」:フィールドをリセットし、変数
position
を0
にして再描画その他
getElementById
やaddEventListener
、アロー関数() => { ~ }
などの、その他の基本的な知識については、僕の過去記事をどうぞ。
【初心者】JavaScript(ES6)の基礎を勉強した
【初心者】JavaScriptで「おみくじゲーム」を作ってみたおわりに
「おみくじゲーム」に引き続き、JavaScriptでミニゲームを作ってみました。
簡単なものですが、アウトプットは楽しいですし、
Qiitaの記事を書くと個人的な復習にもなって、より深い理解につながりますね。引き続き、アウトプット重視でプログラミング学習がんばります。
Twitter フォローしてくれたら喜びます。
- 投稿日:2020-01-21T07:37:30+09:00
JSDocの書き方・出力メモ
最近はJavaScriptを書くことが多く、「仕様書出せ!」と言われるのでJSDocでの記述・出力メモ。
やりたいこと
- ./srcフォルダに機能別に保存され、module.exportsされる関数の仕様を記述・出力する
セットアップ
めんどくさいのでグローバルにインストールします。
npm install -g jsdocサンプル
作業場所の準備
mkdir jsdoc-test cd jsdoc-test mkdir src touch hello.js bye.jshello.jsの記述
モジュールの場合は@moduleを付けてやらないと何も出力されない。
hello.js/** * Helloという文字列を返します。 * @module hello * @param {string} name - 表示したい名前を指定する。 * @return {string} - [Hello + name]という形式で戻る。 */ module.exports = hello = (name) => { return "Hello" + name; }bye.jsの記述
bye.js/** * Helloという文字列を返します。 * @module bye * @param {string} name - 表示したい名前を指定する。 * @return {string} - [Bye + name]という形式で戻る。 */ module.exports = bye = (name) => { return "Bye" + name; }Doc出力
jsdoc srcすると、同じ階層にoutというフォルダが作成される。その中のindex.htmlを見る。
下記のような内容が表示される(味気ない)。右メニューのHelloを見ると下記のような感じ。
基本は以上。
応用1:Home画面に説明文を入れたい
Markdownで説明文を書いて、出力時に指定すれば取り込んでくれるみたい(便利)。
README.md### テストAPIです。 以下の2つを作りました。 * hello() * bye()そして、
jsdoc src README.mdとすると、下記のようにHomeに出力されます。
応用2:Objectを記述する
JSではObjectを引数、戻り値に取ることもありますよね。。。下記のように記述するようです。
object.js/** * Objectを受け取りObjectを返します。 * @module object * @param {Object} user - ユーザーオブジェクト * @param {string} user.name - ユーザの名前。 * @param {number} user.age - ユーザーの年齢。 * @return {Object} result オブジェクトが戻る。 * @return {string} result.status - "OK or NG"。 * @return {string} result.message - メッセージが返ります。 */ module.exports = object = (User) => { return { status: "OK", message: "hoge" } }が、returnはうまく表現できないのかな・・・。
応用3:テンプレートを変更する
しかしJSDocの標準テンプレートはなんでこんなに微妙なんだろ。。。せめて左メニューにしてくれたらいいのに。。。
変え方はひとまずリンクだけ。こちらのサイトに紹介されているので一旦メモ。
- 投稿日:2020-01-21T07:20:46+09:00
【Rails】 DataTables 動的にカラムを変更する方法
はじめに
Railsアプリケーションで DataTables を使う方法を以前にまとめさせていただきました。
DataTables を使ったテーブルのカラムを動的に変更したい需要があると思いますが、まとめられている記事を見かけませんでしたので、こちらにてまとめさせていただきます。
この方法を理解していれば、開発時間を極端に減らして高機能なテーブルを提供することができます。なお、今回の方法は少し強引にカラムを動的に変更しています。
もし、もっといい方法があるということをご存知の方はコメントをいただければ嬉しいです。動的にカラムを変更する方法
前提条件
下記リンクにてDataTablesを実装していること。
このリンク先のコードを元にして、動的にカラムを変更する方法をこちらの記事にて特記したいと思います。
- 【Rails】DataTables 実装方法
値を変更する方法
動的にカラムを変更するには、
users.coffee
のコードを変更することによって可能です。ここでは一例として、id が 3 のときに"あほ"を表示するコードを書いています。
app/assets/javascripts/users.coffee$ -> # *** 省略 *** # user_table へカラムを追加する user_table.setColumns([ # 以下に注目 { data: 'id', title: 'ユーザID', width: '5%' # 以下を追記 render: (data, type, row) -> # data / type / row にどんなデータが入っているか確認。 console.log data console.log type console.log row if data == "3" "あほ" }, { data: 'username', title: 'ユーザ名', width: '25%' }, { data: 'name', title: '名前', width: '30%' }, { data: 'created_at', title: '登録日時', width: '20%' }, { data: 'updated_at', title: '更新日時', width: '20%' }, ]) # *** 省略 ***フォントを変更する方法
つづいて、フォントを変更する方法を紹介します。
一例として、id が 3 のときに赤色の文字で"あほ"と表示するコードを書いています。
htmlタグを使ってそこにcssを埋め込んでいるだけとなります。(少し強引かもしれません。。)app/assets/javascripts/users.coffee$ -> # *** 省略 *** # 以下に注目 { data: 'id', title: 'ユーザID', width: '5%' # 以下を追記 render: (data, type, row) -> if data == "3" "<span style='color: red;'>あほ</span>" }, # *** 省略 ***Bootstrap のレイアウトを導入する
最後に応用技として、 Bootstrap のレイアウトを導入する方法を紹介します。
一例として、id が 3 のときに赤色の文字で"あほ"と表示するコードを書いています。
応用と書きましたが、htmlタグを使ってbootstrapで使用できるclassを付与しているだけとなります。app/assets/javascripts/users.coffee$ -> # *** 省略 *** # 以下に注目 { data: 'id', title: 'ユーザID', width: '5%' # 以下を追記 render: (data, type, row) -> if data == "3" "<div><center><span class='label label-default'>あほ</span></center></div>" }, # *** 省略 ***まとめ
いかがでしょうか。Railsなので動的に値を変更したい需要はかなりあるかと思いますが、DataTables だとドキュメントが英語で読みづらいし、あまり柔軟性がないと考える方もいると思います。
少し強引ではありますが、このような感じで色々な応用をすることも可能ですので、ご自身で色々と試してみるのもいいかもしれません。
- 投稿日:2020-01-21T07:11:06+09:00
花粉症LINE Botのデータをnode.jsを使ってFirebaseに出し入れする(花粉カレンダー作成③)
概要
耳鼻咽喉科の開業医をしています。
今回、以前作成したLINE Botのデータをnode.jsを使ってFirebaseに出し入れできるようにしました。
以前作ったLINE Botの記事はこちら
花粉症の重症度を判定し自分に合う市販薬を教えてくれるLINE Botの作成作成
1.Firebaseno秘密鍵を生成し準備
こちらを参考にします。
サーバーに Firebase Admin SDK を追加するサービスアカウントに移動し下部にある「新しい秘密鍵の生成」ボタンを押します。
次に表示される「キーを生成」ボタンを押します。
すると、すぐに生成されてJSONファイルがダウンロードされます。この段階では、長めの文字数のファイル名.jsonになっています。
ダウンロードしたJSONファイルをserviceAccountKey.jsonに名前を変えて配置します。Realtime DatabaseのページでdatabaseURLを確認します。
Firebaseコンソールにある、Realtime Databaseの中にあるデータが確認できるページでdatabaseURLを確認します。赤枠のところをメモしておきます。
2.Firebaseにデータを記録できるようにする
Firebase関連のインストール
npm i firebase-admin花粉症の重症度を判定し自分に合う市販薬を教えてくれるLINE Botの作成で作成したプログラムに追記していきます。
// Firebase ///////////////////////////////// var admin = require("firebase-admin"); // 1. サービスアカウント鍵を生成しserviceAccountKey.jsonにリネーム var serviceAccount = require("./serviceAccountKey.json"); admin.initializeApp({ credential: admin.credential.cert(serviceAccount), // 2. Realtime DatabaseのページでdatabaseURLを確認して反映 databaseURL: "https://*************.com" }); var db = admin.database(); var ref = db.ref("protoout/studio"); var usersRef = ref.child("messageList"); // LINE ///////////////////////////////////// const express = require('express'); const line = require('@line/bot-sdk'); const axios = require('axios'); const PORT = process.env.PORT || 3000; const config = { channelSecret: '********************', channelAccessToken: '***********************' }; const app = express(); // app.get('/', (req, res) => res.send('Hello LINE BOT!(GET)')); //ブラウザ確認用(無くても問題ない) app.post('/webhook', line.middleware(config), (req, res) => { console.log(req.body.events); //ここのif分はdeveloper consoleの"接続確認"用なので削除して問題ないです。 if(req.body.events[0].replyToken === '00000000000000000000000000000000' && req.body.events[1].replyToken === 'ffffffffffffffffffffffffffffffff'){ res.send('Hello LINE BOT!(POST)'); console.log('疎通確認用'); return; } // Firebaseにも応答を記録 push usersRef.push({ events:req.body.events }); Promise .all(req.body.events.map(handleEvent)) .then((result) => res.json(result)); });3.Firebaseからデータを受信できるようにする
以下のコードを追記します。
// Firebase Listからデータを受信 child added on var refMessageList = db.ref("protoout/studio/messageList"); refMessageList.on('child_added', function (snapshot) { //postbackの場合 if (snapshot.val().events[0].type == 'postback') { console.log('child_added', snapshot.val().events[0].postback.data) userpostback = snapshot.val().events[0].postback.data; } //messageの場合 if (snapshot.val().events[0].type == 'message') { //message-位置情報の場合 if (snapshot.val().events[0].message.type == 'location') { userlat = snapshot.val().events[0].message.latitude; userlong = snapshot.val().events[0].message.longitude; getweather();//気象情報をreturnする関数 } } })4.データベースを確認する
LINEのデータが記録されています。
考察
LINE BotのデータをFirebaseに出し入れすることができました。
今度はFirebaseのデータをWEBカレンダーに表示できるようにしたいと思います。
- 投稿日:2020-01-21T04:48:26+09:00
Svelteでテストがしたい
Svelte 良いですよね。
情報が少なかったので、Store/コンポーネント/E2Eでテストする方法をまとめました。手探りでやっているので、うまく行かないところなどがありましたら、コメントまでお願いいたします。
GitHub
https://github.com/nishinoshake/svelte-minimal-testingSvelteの環境構築
sveltejs/templateを持ってくる。
npx degit sveltejs/template svelte-app cd svelte-app npm install npm run devこれで「HELLO WORLD!」と表示されるはずです。
http://localhost:5000テスト用に少し変更
最小限のテストができそうな形に修正します。
ボタンをカウントアップするだけのシンプルなコンポーネントです。src/main.jsimport App from './App.svelte' const app = new App({ target: document.body }) export default appsrc/stores.jsimport { writable } from 'svelte/store' export const count = writable(0) export const increment = () => count.update(count => count + 1)src/App.svelte<script> import { count, increment } from './stores.js' </script> <button on:click={increment}>{$count}</button>Storeのテスト
まずは一番テストしやすそうな状態管理の部分から。
Jestをインストール
ドキュメントの通りにインストールします。ついでにBabelも。
https://jestjs.io/docs/ja/getting-startednpm install -D jest babel-jest @babel/core @babel/preset-envbabel.config.jsmodule.exports = { presets: [ [ '@babel/preset-env', { targets: { node: 'current' } } ] ] }jest.configmodule.exports = { verbose: true, transform: { "^.+\\.js$": "babel-jest" } }package.json{ "scripts": { "test:unit": "jest test/unit" } }テストを作成
Storeの値はget()で取得できます。
test/unit/stores.test.jsimport { get } from 'svelte/store' import { count, increment } from '../../src/stores' test('インクリメント', () => { expect(get(count)).toBe(0) increment() expect(get(count)).toBe(1) })テストを実行
npm run test:unit
コンポーネントのテスト
次にコンポーネントのテストを。
テストツールのインストール
コンポーネントのレンダリングなどをやってくれるツールをインストールします。svelte-testing-libraryというのを使いました。Jestでテストするために必要なパッケージも合わせいれてます。
npm install -D @testing-library/svelte @testing-library/jest-dom jest-transform-svelteJestの設定も少し変更します。
jest.config.jsmodule.exports = { verbose: true, transform: { "^.+\\.js$": "babel-jest", "^.+\\.svelte$": "jest-transform-svelte" }, "moduleFileExtensions": ["js", "svelte"], "setupFilesAfterEnv": ["@testing-library/jest-dom/extend-expect"] }テストを作成
test/unit/App.test.svelteimport { render, fireEvent } from '@testing-library/svelte' import App from '../../src/App.svelte' test('コンポーネントのインクリメント', async () => { const { container } = render(App) const button = container.querySelector('button') expect(button.textContent).toBe('0') await fireEvent.click(button) expect(button.textContent).toBe('1') })テストを実行
npm run test:unit
E2Eテスト
ツールはCypressにしました。
Cypressをインストール
テストサーバーの起動を待つ、start-server-and-testという便利なパッケージも合わせてインストールしてます。
npm install -D cypress start-server-and-testpackage.json{ "scripts": { "build": "rollup -c", "dev": "rollup -c -w", "start": "sirv public", "cy:run": "cypress run", "test:unit": "jest test/unit", "test:e2e": "start-server-and-test start http://localhost:5000 cy:run" } }Cypressの設定
デフォルトではプロジェクト直下の
cypress
ディレクトリを見に行くのですが、test
にまとめたかったので少し調整したのと、サーバーのURL指定も追加してます。cypress.json{ "video": false, "baseUrl": "http://localhost:5000", "fixturesFolder": "test/e2e/fixtures", "integrationFolder": "test/e2e/integration", "screenshotsFolder": "test/e2e/screenshots", "videosFolder": "test/e2e/videos", "pluginsFile": false, "supportFile": false }テストの作成
test/e2e/index.test.jsit('E2Eのインクリメント', () => { cy.visit('/') cy.get('button').should('have.text', '0') cy.get('button').click() cy.get('button').should('have.text', '1') })アプリケーションのビルド
ビルドしたものに対してテストしたいので事前に。
npm run buildテストの実行
npm run test:e2eおわり
別の記事にSvelteの魅力もまとめているのでぜひ。
https://qiita.com/nishinoshake/items/46a64591c6411af68af1GitHub
https://github.com/nishinoshake/svelte-minimal-testing
- 投稿日:2020-01-21T00:28:07+09:00
2020年 ITカンファレンスまとめ
2020年に開催されるITカンファレンス
1月20日時点のまとめです。
随時、更新します。言語
PyCon JP
Pythonに関するカンファレンス
日程: 8月28日、29日の予定
場所: 大田区産業プラザPiO
公式サイトPHPカンファレンス
PHPに関するカンファレンス
日程: 10月11日
場所: 大田区産業プラザPiO
2019公式サイト
2020公式サイトPHPerKaigi
PHPに関するカンファレンス
日程: 2月9日 16:30〜
2月10日 10:00〜
2月11日 10:00〜
場所: 練馬区立区民・産業プラザ Coconeriホール
2020公式サイトJSConf JP
JavaScriptに関するカンファレンス
開催不明
去年
日程:11月30、12月1日
場所:アーツ千代田 3331
2019公式サイトRubyKaigi
Rubyに関するカンファレンス
日程:4月9日、11日
場所:長野県 まつもと市民芸術館
公式サイトGo Conference
Goに関するカンファレンス 春、秋開催
開催不明
去年
日程:秋 10月28日
場所:みどりコミュニティセンター
2019公式サイトYAPC
Perlに関するカンファレンス
日程:3月27日、28日
場所:京都
公式サイトJJUG
Javaに関するカンファレンス
開催不明
去年
日程:春 5月18日、秋 11月23日
場所:ベルサール新宿グランドコンファレンスセンター
春 公式サイト
秋 公式サイトTSConf JP
TypeScriptに関するカンファレンス
日程:2月22日
場所:NAVITIME JAPAN
公式サイトtry! Swift
Swiftに関するカンファレンス
日程:3月18日,19日
場所:ベルサール渋谷ファースト
公式サイトFW
DjangoCongress JP
Djangoに関するカンファレンス
日程: 6月20日
場所: 長野市生涯学習センター
公式サイトLaravel JP Conference
Larabelに関するカンファレンス
日程:3月21日
場所:グランパークカンファレンス
公式サイトCakeFest
CakePHPに関するカンファレンス
開催日不明
今年は海外の可能性大
公式サイトVueFes Japan
Vueに関するカンファレンス
開催不明
去年
日程: 10月12日
場所: TOC五反田メッセ
公式サイトReact Conf Japan
Reactに関するカンファレンス
日程:5月21日
場所:株式会社ナビタイムジャパン
公式サイトng-japan
Angularに関するカンファレンス
開催不明
去年
日程:7月13日
場所:Google Tokyo Office
公式サイトNodeTokyo
Node.jsに関するカンファレンス
開催不明
去年
日程: 10月5日
場所: 丸の内 vacans
公式サイトスマホ
PWA Night
PWAに関するカンファレンス
日程:2月1日
場所:Abema Towers 10F セミナールーム
公式サイトiOSDC
iOSに関するカンファレンス
開催不明
去年
日程:9月5日、6日、7日
場所:早稲田大学 理工学部西早稲田キャンパス63号館
2019公式サイトDroidKaigi
Androidに関するカンファレンス
日程:2月20日、21日
場所:五反田TOCビル 13F
公式サイトAndroid Bazaar and Conference
開催不明
去年
日程:5月26日
場所:東海大学 高輪キャンパス
2019公式サイトその他
AWS SUMMIT TOKYO/OSAKA
日程:5月13日、14日、15日
場所:パシフィコ横浜
日程:6月30日
場所:ホテルニューオータニ大阪
公式サイトGoogle Cloud Next
開催不明
去年
日程:7月30日、8月1日
場所:東京プリンスホテル
ザ プリンス パークタワー東京
2019公式サイトMicrosoft Ignite The Tour Tokyo
Microsoftに関するカンファレンス
開催不明
去年
日程:12月5日、6日
場所:ザ・プリンス パークタワー東京
公式サイトPostgreSQL Conference Japan
開催不明
去年
日程: 11月15日
場所: AP品川9階
2019公式サイトAdobe MAX Japan
Adobeに関するカンファレンス
日程:11月24日
開催:パシフィコ横浜
公式サイトUnite Tokyo
Unityに関するカンファレンス
開催不明
去年
日程:9月25日、26日
場所:グランドニッコー東京 台場
2019公式サイトCEDEC
ゲームに関するカンファレンス
日程:9月2日、3日、4日
場所:パシフィコ横浜 ノース
公式サイトObject-Oriented Conference
Object指向に関するカンファレンス
日程:2月16日
場所:お茶の水女子大学
公式サイトVimConf
Vimに関するカンファレンス
開催不明
去年
日程:11月3日
場所:アキバホール
2019公式サイトDigital Thinkers Conference
デザインに関するカンファレンス
日程:1月23日、24日
場所:イイノホール&カンファレンスセンター
公式サイトMeetUp
Docker Meetup Tokyo
Dockerに関するミートアップ
公式サイトKubernetes Meetup Tokyo
Kubernetesに関するミートアップ
公式サイト最近、Twitter始めました。
ぜひ友達募集中です!
https://twitter.com/apasn1
- 投稿日:2020-01-21T00:07:07+09:00
JavaScriptの正規表現
正規表現記号 意味 . 任意の一文字 + 直前の文字を1回以上繰り返す最長の文字列 * 直前の文字を0回以上繰り返す最長の文字列 ? 直前の文字を0~1回繰り返す最長の文字列 +? 直前の文字を1回以上繰り返す最短の文字列 *? 直前の文字を0回以上繰り返す最短の文字列 ?? 直前の文字が0~1回繰り返す最短の文字列 | OR \ 直後の正規表現記号をエスケープ [ ] 括弧内のいずれか一文字 [^ ] 括弧内の文字列以外 [a-Z] 括弧内のみで使える文字の範囲指定 ( ) {n} 直前の文字をn回繰り返す {n,} 直前の文字を繰り返す最小回数 {n,m} 直前の文字を繰り返す最小回数と最大回数の範囲で最長の文字列 {n,m}? 直前の文字を繰り返す最小回数と最大回数の範囲で最短の文字列
標準エスケープ文字 意味 \t タブ \r 改行 \n 改行 \d 全ての数字 \D 全ての数字以外の文字 \s 垂直タブ以外の全てのスペース \S 全てのスペース以外の文字 \w アルファベット、アンダーバー、数字 \W アルファベット以外、アンダーバー以外、数字以外
位置の指定 意味 ^ 直後の文字が行頭 $ 直前の文字が行末 < 単語の先頭 > 単語の直継 \b 単語の先頭か末尾 \B 単語の先頭以外か末尾以外 \A ファイルの先頭 \z ファイルの末尾 \G 直前の一致文字列の末尾
置換・変換 意味 \0 一致した文字列全体に置換 \1-\9 一致した文字列の1-9番目に対応する文字列に置換 \l 直後の一文字を小文字に変換 \L...\E 挟まれた文字列を小文字に変換 \u 直後の一文字を大文字に変換 \U...\E 挟まれた文字列を大文字に変換
用例 結果 a....a abcdefa a*a aa
aaaaaaaaa.*a aba
abbbbbbbbbaa+a aaa
aaaaaaaaaaa
- 投稿日:2020-01-21T00:04:43+09:00
ElectronでcontextBridgeによる安全なIPC通信
はじめに
Electronの情報って、検索すると沢山出てくるのに、ところどころみな違っていて見極めが難しいですよね。まだまだ私自身よくわかっていないですが、調べた情報を共有します。
現時点での結論として、セキュアなIPC通信にはcontextBridgeを使おう、ということらしいです。
とはいえ、Electronの状況はversionによってかなり変わるようなので、以下の際内容には注意してください。こちらで検証した時点でのElectronのversionは7.1.9です。
Electronにおけるセキュアな設計とは
前提として、Electronでは、メインプロセスと、webページ画面として動くレンダラープロセスが立ち上がります。最初にelectronコマンドの引数として指定したjsファイル(今回はmain.jsとします)がmainプロセス上で実行され、
$ electron ./main.jsその中で
BrowserWindow.loadURL()
関数などで読み込まれたhtmlがレンダラープロセス上で起動します(今回はindex.htmlとします)。また、index.html上で読み込まれたjsファイルもレンダラープロセス上で実行されます。たたき台として、以下のようなコードが最小コードとしましょう。
/* main.js, case 0 (initial) **************************/ const {electron,BrowserWindow,app} = require('electron'); let mainWindow = null; const CreateWindow = () => { mainWindow = new BrowserWindow({width: 800, height: 600}); mainWindow.loadURL('file://' + __dirname + '/index.html'); mainWindow.webContents.openDevTools(); mainWindow.on('closed', function() { mainWindow = null; }); } app.on('ready', CreateWindow);<!--index.html, case 0 (initial) --> <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Test</title> </head> <body> <button id="button1">test1</button> </body> <script type = "text/javascript"> //適当なプログラム const electron = require('electron');//これがエラーになる const {ipcRenderer} = require('electron');//これもエラー </script> </html>ここで、昔のversionのElectronではレンダラープロセス上でもファイル読み書きなどのnodeの便利なメソッドが使えたわけですが、最近はdefaultでは使えなくなっているそうです。ですので、上記の様にレンダラープロセス上の「適当なプログラム」の部分で
require('electron')
と書いて実行しようとすると、"Uncaught ReferenceError: require is not defined at index.html"のようなエラーメッセージが出ます。じゃあ、ファイル読み書きなどのnodeの機能はメインプロセス上だけでやろう、という方針を取るにしても、レンダラープロセスからの信号や情報をメインプロセスへ伝える手段がいるわけです。プロセス間の通信は
IPC通信
としてElectronのAPIが用意されているものの、最低限レンダラープロセス上での通信処理を司るipcRenderer
が欲しくなります(公式docs)。しかし、requireが使えないのでそれすら取得できません。どうしましょう。
巷の情報
検索して出てくる情報は以下のようなものが多いです。
nodeIntegration: true
にすればよい。セキュアにするには
nodeIntegration: false
のままにすべし。その代わりpreloadを使おう。
preload内で準備したオブジェクトや関数をレンダラープロセスのjsで使うためには、(
global
や)window
の変数に追加することでインスタンスを渡そう。あるversion以降、プロセス間で
window
が同一のオブジェクトではなくなった。よって受け渡しできない。同一オブジェクトにするにはcontextIsolation: false
としよう。いやいや、セキュアにするには
contextIsolation: true
のままにしよう。
contextBridge
を使えば、nodeIntegration: false,contextIsolation: true
でもIPC通信できる[^1][^2]。巡り巡って、どうやら、7番の方法で解決みたいですが、それ以前の手立ても含めて以下にまとめていきます。
方法1(情報1): nodeIntegration: true
nodeIntegrationというのは、メインプロセスでウィンドウを生成するとき位のオプションで指定します。先のmain.jsにおいて、
BrowserWindow
の生成部分のコードを以下の様に書き替えます。/* main.js, case 1 */ // ~略~ // const CreateWindow = () => { mainWindow = new BrowserWindow({width: 800, height: 600, webPreferences: { nodeIntegration: true, } }); // ~略~ //これだけで、レンダラープロセスで
require
関数が使えるようになります。しかし、デバッグコンソールには"Electron Security Warning (Insecure Content-Security-Policy)"というwarningメッセージがでてきて、なにやら危ないようです。XSSの危険が大きいということで、あまりお勧めできないようです。方法2(情報2-6):preloadを使う
では、
nodeintegration: false
としながら、レンダラープロセスでせめてIPC通信だけでもするにはどうするのか。そこで出てくるのがpreloadで追加jsを先行して読ませる方法です。読ませるjsをpreload.jsとします。このpreload.jsにおいてはnode.jsの機能、つまりrequire
関数が使えるので、これをグローバルなオブジェクト変数として記録します。それをレンダラープロセスから使うということになります。コードで書くと、次のようになります。/* main.js, case 2 */ //ipcMainの追加 const {electron,BrowserWindow,app,ipcMain} = require('electron'); let mainWindow = null; const CreateWindow = () => { mainWindow = new BrowserWindow({width: 800, height: 600, webPreferences: { nodeIntegration: false, //ここはfalseのまま contextIsolation: false, //これをfalseに preload: __dirname + '/preload.js' //preloadするjs指定 } }); mainWindow.loadURL('file://' + __dirname + '/index.html'); mainWindow.webContents.openDevTools(); mainWindow.on('closed', function() { mainWindow = null; }); } app.on('ready', CreateWindow); //IPCメッセージの受信部(レンダラープロセスから送られる)// ipcMain.on("msg_render_to_main", (event, arg) => { console.log(arg); //printing "good job" });/* preload.js, case 2*/ const {ipcRenderer} = require('electron'); window.MyIPCSend = (msg)=>{ ipcRenderer.send("msg_render_to_main", msg); }<!-- index.html, case 2 --> <!DOCTYPE html> <html> ~~略~~ <script type = "text/javascript"> //適当なプログラム const button1 = document.getElementById("button1"); button1.addEventListener("click", (e)=>{ window.MyIPCSend("good job");}); </script> </html>まず、main.jsでは、
BrowserWindow
の生成のoptionにpreload
とcontextIsolation
の項目を追加しています。またIPCメッセージの受信部としてipcMain.on
を設定しています。preload.jsでは
require
が利用できるので、グローバル変数としてwindow.MyIPCSend(msg)
関数を追加し、その中でipcRenderer
を使ったメッセージ送信の機能を持たせます。ここからメインプロセスのipcMain.on
へメッセージを送ります。index.htmlではボタンを押したときに
window.MyIPCSend(msg)
関数を呼び出します。これはpreload.jsで定義したものですが、グローバルなwindowオブジェクトに保持されているので使えるようです。このような形でIPCメッセージだけでもやり取りできれば、それで必要な情報を送り、node関連の機能を使った処理は全てメインプロセスへ押し付けてしまうこともできるでしょう。
ところがこの方法でも、
contextIsolation: false
が必要です。あるversionからデフォルトではcontextIsolation: true
となったようです。そしてセキュアにするには、ここもtrueがよいと。しかし、trueとすると、preload.jsから呼び出したwindowと、index.htmlで呼び出すwindowのインスタンスが別物になってしまいます。よって、window.MyIPCSend(msg)
関数をindex.htmlから呼び出しても、定義されていない旨のエラーメッセージが出ます。方法3(情報7):contextBridgeを利用する
さて、
nodeIntegration: false
かつcontextIsolation: true
のままでIPC通信する手段として、contextBridge
というElectron APIがあるそうです[^1]。これはElectronで公式に提案されたセキュアなプロセス間通信の実現のためのAPIだそうです(これを見つけた時は、嬉しくて叫んじゃいました)。コードは次のようになります。
/* main.js, case 3 (final) */ // ~~略~~ ここまでcase2と同じ// mainWindow = new BrowserWindow({width: 800, height: 600, webPreferences: { nodeIntegration: false, //ここはfalseのまま contextIsolation: true, //trueのまま(case2と違う) preload: __dirname + '/preload.js' //preloadするjs指定 } }); // ~~略~~ 以後もcase2と同じ///* preload.js, case 3 (final)*/ const { contextBridge, ipcRenderer} = require("electron"); contextBridge.exposeInMainWorld( "api", { send: (data) => { ipcRenderer.send("msg_render_to_main", data); } } );<!-- index.html, case 3 (final) --> <!DOCTYPE html> <html> ~~略~~ <script type = "text/javascript"> //適当なプログラム const button1 = document.getElementById("button1"); button1.addEventListener("click", (e)=>{ window.api.send("god job");}); </script> </html>さて、main.jsは方法2と比べて
contextIsolation: true
に変えただけです。大きく変わったのはpreload.jsです。electronからオブジェクト
contextBridge
を取り出し、exposeInMainWorld()
によってグローバルな関数send()
を登録しています。ここで登録した関数は、レンダラープロセスのindex.htmlの中からもwindow.api.send()
として呼び出すことができます。めでたし、めでたし。
注意点
contextBridgeはとっても良さそうなAPIですが、Electronのドキュメント[^3]には次のように書かれています。
"The
contextBridge
API has been published to Electron's master branch, but has not yet been included in an Electron release."一応、私の環境のversion7.1.9では使えていますが、いつから使えるようになったのかはちょっと不明なので、気を付けてください。
感想
HTML+Javascriptでブラウザ上だけでほぼ動くものを作ってしまえば、パッケージングはElectronですぐにできると思っていた時期が僕にもありました。。。
この記事がだれかの参考になれば幸いです。とはいえ、なにぶんJavascriptはライト勢なので、間違いもたくさんありそう。ご指摘いただければ大変嬉しいです。
References