- 投稿日:2020-03-27T23:43:26+09:00
Spreadsheetでラクしたい Google Apps Script ソースコードテンプレート
以下ページの転載になります。ご了承ください。
Spreadsheetでラクしたい Google Apps Script ソースコードテンプレート - Yuto Hongo Portfolio
[ひとことで言うと、こんな記事]
SpreadSheetでの情報の【取得】【更新】の基本的なメソッドをあらかじめGASのソースとして、準備しました。
[こんな人におすすめ]
- 表計算ソフトでのデータ処理の作業がつらいと感じている方
- データを編集するような作業を自動化したいと感じている方
このテンプレートで少しでも楽になる方が増えればと思います。
[目次]
- 定常作業がめんどくさいので、Google Apps Scriptで自動化したい
- 準備したSpreadsheet編集のサンプルコードの概要
- 実際のサンプルコード
定常作業がめんどくさいので、Google Apps Scriptで自動化したい
皆さんも、データ入力や編集の作業って普段から行ってたりしないでしょうか? 特になにかのデータのマスター管理とかをなさっていたりする方は多いと思います。
本当にめんどくさいです。
実は、Googleに準備されているサービスのいくつかは、Google Apps Scriptというものでなにかしらの処理をしてもらうことが可能です。
Apps Script | Google Developersが公式サイトとなります。
TOPページをみただけでも、様々なサービスに対して利用できますね。
例
- Spreadsheet
- Gmail
- Google Drive
- Google Form
- etc...今回はSpreadSheetのデータ処理を行う際に、最初に準備しておくと作業が楽になりそうなソースコードのテンプレートを準備させていただきました。
準備したスプレッドシート編集のサンプルコード概要
GASの実行はV8ランタイムが前提
V8 Runtime Overview | Apps Script | Google Developers
少し前にアップデートされたそうです。
スプレッドシートの1行目はテーブルヘッダ
Column1 Column2 Column3 Column4 … data1-1 data1-2 data1-3 data1-4 … … … … … … 各カラムにどんな情報が入っているのかの説明行としておいてある想定です。
1行のデータをモデルとして捉える
1行分のデータをモデルとして定義しています。今回は column1~4 + SpreadSheet用のデータをメンバとして定義しています。
必要最低限の情報取得、書込の機能だけを記述
以下のような機能のみを記述していたりします。
情報の取得
- resolveAll … スプレッドシート内にすでにある情報をすべて取得し、 Modelの配列 として返却
- resolveByRowNumber … スプレッドシート内で○○番目にある情報を取得し、 Model として返却
情報の書込
- storeAll … Modelの配列 をすべてシートに書き込む
- storeByRowNumber … Model をスプレッドシート内で○○番目に書き込む
実際のサンプルコード
以下に実際のサンプルコードを記述していきます。
main.gs
// Get Endpoint function doGet(e) { } // Post Endpoint function doPost(e) { } // Main function main() { // TODO:処理の記述 }model.gs
/** * SpreadSheetの1行を表したModelの準備 */ class _Model { // TODO:必要なカラムに応じて準備 constructor( column1, column2, column3, column4 ) { this.column1 = column1 this.column2 = column2 this.column3 = column3 this.column4 = column4 this.sheetRow = [ column1, column2, column3, column4, ] } }modelRepository.gs
/** * SpreadSheetの情報を取得/編集する処理 * (※ 必要に応じて準備してください) */ /** * 情報をすべてModelとして取得する */ function _modelRepository_resolveAll(sheet) { var startRow = 2 // 1行目がテーブルヘッダ var startCol = 1 var lastRow = sheet.getLastRow() var numRows = lastRow - 1 // 1行目がテーブルヘッダ var lastCol = sheet.getLastColumn() // まだ情報がない場合 if (numRows === 0) return [] // TODO:必要なカラムに応じて準備 var sheetDatas = sheet.getSheetValues(startRow, startCol, numRows, lastCol) return sheetDatas.map(sheetRow => new _Model(sheetRow[0], sheetRow[1], sheetRow[2], sheetRow[3])) } /** * 上から○○番目のデータをModelとして取得 */ function _modelRepository_resolveByRowNumber(sheet, rowNumber) { var startRow = rowNumber + 1 // 1行目がテーブルヘッダ var startCol = 1 var numRows = 1 var lastCol = sheet.getLastColumn() // TODO:必要なカラムに応じて準備 var sheetDatas = sheet.getSheetValues(startRow, startCol, numRows, lastCol); var models = sheetDatas.map(sheetRow => new _Model(sheetRow[0], sheetRow[1], sheetRow[2], sheetRow[3])) return models[0] } /** * すべてのModelをSpreadSheetに保存する */ function _modelRepository_storeAll(sheet, models) { // 既存データをすべて消去 _truncateData(sheet) var startRow = 2 // 1行目がテーブルヘッダ var startCol = 1 var numRows = models.length var lastCol = sheet.getLastColumn() // 更新情報がない場合 if (numRows !== 0) { var updateSheetRange = sheet.getRange(startRow, startCol, numRows, lastCol) var sheetRows = models.map(model => { return model.sheetRow }) updateSheetRange.setValues(sheetRows) } } /** * 上から○○番目にModelの情報を保存する */ function _modelRepository_storeByRowNumber(sheet, rowNumber, model) { var startRow = rowNumber + 1 // 1行目がテーブルヘッダ var startCol = 1 var numRows = 1 var lastCol = sheet.getLastColumn() var updateSheetRange = sheet.getRange(startRow, startCol, numRows, lastCol) var sheetRows = [model.sheetRow] updateSheetRange.setValues(sheetRows) } /** * シート情報をすべてクリアする */ function _truncateData(sheet) { var startRow = 2 // 1行目がテーブルヘッダ var startCol = 1 var lastRow = sheet.getLastRow() var numRows = lastRow - 1 // 1行目がテーブルヘッダ var lastCol = sheet.getLastColumn() // まだ情報がない場合 if (numRows !== 0) { var clearSheetRange = sheet.getRange(startRow, startCol, numRows, lastCol) clearSheetRange.clearContent() } }sheet.gs
/** * 編集対象のSpreadSheetの情報を取得する */ function _getSheet() { // TODO:利用するSpreadSheetに応じて準備 var SHEET_URL = '[SpreadsheetのURL]'; var spreadSheetPage = SpreadsheetApp.openByUrl(SHEET_URL); var sheet = spreadSheetPage.getSheetByName('[対象シートの名前]'); return sheet }
「もっとソースコードをこうしたらきれいに書けます」や「もっとこういう機能入れましょう」などございましたら、以下プルリクエスト等をよろしくお願いいたします。
最後までお読みいただき、誠にありがとうございます。様々なことを学んでいきたいと思っていますので、アドバイス等いただけると幸いです。
- 投稿日:2020-03-27T23:06:54+09:00
[JavaScript] 動的にinput[type=file]を組み立てて画像をアップロードしたらBase64が手に入るコード
画像アップロードする時に、以下のように
display: none
をつかってブラウザ本来のinput[type=file]
をみえないようにした上で独自レイアウトのボタンで画像アップロードするような実装を時々みかけます。<label for="hoge">画像アップロード</label> <input id="hoge" type="file" style="display: none">で、
addEventListener
でファイル変更検知を行ったりと。
けれど、上記のようにするとマークアップがある程度拘束されてしまいます。やりたいことはボタンをクリックしたら画像をアップロードしてBase64を手にいれるとかそういう話なので、クリックイベントを仕込んで以下のコードを実行すればOKです。<div style="width: 0; height: 0; overflow: hidden;"> <input id="browserPhotoUploader" type="file" accept="image/*" /> </div>public getPictureFromBrowser() { /* * input[type=file] を組み立て */ const inputFile: HTMLElement = document.querySelector('input#browserPhotoUploader'); return new Promise(resolve => { /* * input[type=file] の変更(change)を検知したら、ファイルをアップロードしてresolveする */ inputFile.addEventListener( 'change', (e) => { const file = e.target.files[0]; const reader = new FileReader(); reader.onload = (fileInput => { /* * アップロードされるファイルが画像かを簡易判定 */ if (file.type.indexOf('image') < 0) { reject(null); } return (event) => { resolve(event.target.result); }; })(file); reader.readAsDataURL(file); }, false, ); /* * input[type=file] をクリック * イベントリスナーよりあとに実行されるように */ inputFile.click(); }); }このコードだったらHTML側は
<button (click)="getPictureFromBrowser()">画像アップロード</button>だけで済みますね。
display:none
せずにすむのでシンプル!それでは、また。
おまけ: TypeScript版
any
使ってるのちょっと何ですが・・・。public getPictureFromBrowser(): Promise<string> { const inputFile: HTMLElement = document.querySelector('input#browserPhotoUploader'); return new Promise(resolve => { inputFile.addEventListener( 'change', (e: any) => { const file = e.target.files[0]; const reader = new FileReader(); reader.onload = (fileInput => { if (file.type.indexOf('image') < 0) { reject(null); } return (event) => { resolve(event.target.result); }; })(file); reader.readAsDataURL(file); }, false, ); inputFile.click(); }); }
- 投稿日:2020-03-27T21:13:02+09:00
Kinx 実現技術 - Garbage Collection
Garbage Collection
はじめに
「見た目は JavaScript、頭脳(中身)は Ruby、(安定感は AC/DC)」 でお届けしているスクリプト言語 Kinx。作ったものの紹介だけではなく実現のために使った技術を紹介していくのも貢献。その道の人には当たり前でも、そうでない人にも興味をもって貰えるかもしれない。
前回のテーマは JIT。今回のテーマは Garbage Collection。
- 参考
- 最初の動機 ... スクリプト言語 KINX(ご紹介)
- 個別記事へのリンクは全てここに集約してあります。
- リポジトリ ... https://github.com/Kray-G/kinx
- Pull Request 等お待ちしております。
GC は特別なことはしていない、というよりむしろ複雑なことはせずに、シンプルにしました。一番の目的はリソース管理というのも勿論あるものの、フリー・オブジェクトのキャッシングにある。malloc/free の回数を減らす。これが結構パフォーマンスに影響する。
実現手法
マーク・アンド・スイープ
ストップ・ザ・ワールドのマーク・アンド・スイープで、一旦全ての処理を止めて GC する。(あえて Kinx の)疑似コードであらわすと次のような感じ。
function mark(p) { // Stop the recursive check return if (p.mark); p.mark = true; // Check the mark recursively p.innerObjects.each(mark); } function markAndSweep(stack, context) { // Initialization context.aliveList.each(&(p) => { p.mark = false; }); // Mark stack.each(mark); // Sweep context.aliveList = context.aliveList.filter(&(p) => { if (!p.mark) { context.deadList.push(p); } return p.mark; }); }マーク
まず、マークと呼ばれるフェーズで生きているオブジェクト全てにマークを付けていく。ここでマークを漏らすと生きているのに回収されてしまっておかしくなるので漏らさないように。例のレキシカル・スコープや、関数オブジェクトに括り付いたフレーム等も全て対象。
マークの際のルートとなるのはスタック。スタック上のオブジェクトをルートに参照を全て辿って到達するオブジェクトに対し、片っ端からマークしていく。オブジェクトは循環参照しているケースがあるので、一度マークを付けたら次にまた到達したときには何もしないようにガードしておく。
また、Kinx ではスタック以外のルートとして以下がある。
- 独立して保持している例外オブジェクト。
- 正規表現リテラルの管理リスト。
晴れて全てのオブジェクトにマークが付いたら、スイープのフェーズに入る。
スイープ
オブジェクトは全て alive リストと dead リストで管理されている。ただし、alive の方は線形リスト、dead の方はベクターである。
なぜなら、alive リストは死んだオブジェクトを途中から引っこ抜いていく形で使われるのに対し、dead リストは死んだオブジェクトの再利用に使われるだけなので途中から引っこ抜くことがないため。こんな感じ。
aliveList -> obj1 -> obj2 -> obj3 -> obj4 -> obj5 -> ... -> objN -> ... ~~~~ dead! ↓ ,---> To deadList / aliveList -> obj1 -> obj2 obj3 obj4 -> obj5 -> ... -> objN -> ... | ↑ `---------------'Kinx ではオブジェクト・サイズごとに全て異なるアロケーターを持ち、別々の alive/dead リストで管理することにしてある。そうすることで、オブジェクト再利用時の処理を dead リストからの pop 一発でいけるようにしている。
スイープ・フェーズでは alive リスト全てチェックし、マークの付いていないオブジェクトをリストから外して dead リストに push。
gctick
回収オブジェクトが多くないと GC するのは無駄。なので、まずは一定間隔で GC するようになっている。そのカウンタとして gctick が用意してある。一定回数インストラクションを実行したら GC がキックされる。
今後 GC 中のオブジェクト状況をプロファイルして gctick の値をコントロールしようとは思っている。
雑感
本来管理すべきリソースはメモリだけではない。ファイル・ディスクリプタやソケット等 open/close が伴うものは適切にリソース管理しなくてはならない。
C++ では RAII という機構でほぼほぼ気にせず華麗にリソース管理できるようにしていたのが、メモリ以外は
try-finally
で自分で管理する必要性が出てきているのはもしかして退化なのでは?、といささか感じなくもない。C++std::unique_ptr<FILE, decltype(&fclose)> fp(fopen(filename, "r"), fclose);に対して、
Kinxfunction open(filename, mode, func) { var f; try { f = new File(filename, mode); return func(f); } finally { f.close(); } }。。。C++ のほうが簡単に書けるな。
まあ、GC するときに close するようにはしてはいるもののタイミングは予測不能。その性質上、予測不能でも問題ないものにしか適用できない。また、迂闊にオブジェクトが残ったりするとシステム側でリソース数に上限があったりするから、ちゃんと解放してあげないといけない。これを GC 的アルゴリズムの(予測不可能な世界の)中で実現するのは難しい。参照カウンタくらいか。
実は Kinx でも XML ドキュメント等は GC の時に解放されるが、解放するかどうかの判断は参照カウンタで実現している。XML ドキュメントは自身が管理する XML ノードから参照されているため、全ての管理対象 XML ノードがフリー状態になっていないと解放できない。したがって参照カウンタ。以下がソース。
今後
今後、GC を改善するとしたら以下のような感じ。ただし、GC のような裏方は本来の仕事を邪魔しない程度に関わるのが筋。GC が明らかにボトルネックになっているといったケース、その機能が言語本来の機能要求を満たすのに必要、という以外、優先度を上げるべきではないだろう。
- gctick コントロール
- Force GC ... これは必要。
- native 中の GC
- インクリメンタル GC
おわりに
Garbage Collection は学術的にも奥が深く、ハマったらここだけでも相当なパワーが必要な領域。現実的な範囲で実用上の要求事項が満たせるレベルで動作させることを一番に考えてこういう実装に。
余裕ができたら高度な GC を取り入れても良いとは思うが、何事もプロファイルが重要。手を付けるならちゃんとボトルネックになってることを確認してからですね。
尚、GC 関連の処理は以下のソースにあります。
では、次は Fiber の予定。
- 投稿日:2020-03-27T21:00:40+09:00
HTML5 <input type="datetime-local"> に現在時刻を設定するには
HTML5 で使えるようになった、
<input>
タグのtype="datetime-local"
` についてです。
日付と時刻を設定できるフォームです。使いやすいとは言えないんですけど、カレンダーで入力できるのでちょっとしたテストをするのに便利です。https://developer.mozilla.org/ja/docs/Web/HTML/Element/Input/datetime-local
Web ページを開いたときに、現在時刻を設定するコードは次のようになります。
toISOString() は UTC が返るので、タイムゾーンを考慮する必要があります。
そこで、getTimezoneOffset() のぶんを、現在時刻からずらします。単位は分です。<input type="datetime-local" id="cal">window.addEventListener('load', () => { const now = new Date(); now.setMinutes(now.getMinutes() - now.getTimezoneOffset()); document.getElementById('cal').value = now.toISOString().slice(0, -1); });なお、value として設定できるのは、次の形式のいずれかです。
yyyy-MM-ddThh:mm
yyyy-MM-ddThh:mm:ss
yyyy-MM-ddThh:mm:ss.SSS
この形式に従わないものを設定しようとすると、開発者コンソールに次のエラーが表示されます。
The specified value "XXX" does not conform to the required format. The format is "yyyy-MM-ddThh:mm" followed by optional ":ss" or ":ss.SSS".
コーディングの例を https://codepen.io/takatama/pen/vYOvEQP に置きました。
- 投稿日:2020-03-27T20:51:55+09:00
Nuxt.js + Vue.js + Vuex によるカウンターのサンプルコード
概要
- Nuxt.js + Vuex を使用して「+」「-」ボタンで数値が増減するカウンターを作成する
今回の環境
- Node.js 13.12.0
- Nuxt.js 2.12.1
Nuxt.js + Vuex によるカウンターのサンプルコード
ソースコード一覧
├── package.json ├── pages │ └── counter.vue └── store └── counter.jspackage.json
{ "name": "my-app", "dependencies": { "nuxt": "2.12.1" } }pages/counter.vue
<template> <div> <p>{{ count }}</p> <p> <button @click="increment">+</button> <button @click="decrement">-</button> </p> </div> </template> <script> export default { computed: { count () { console.log('Call the computed count') return this.$store.state.counter.count } }, methods: { // 「+」ボタンクリック時に呼ばれる increment () { console.log('Call the methods increment') this.$store.commit('counter/increment') }, // 「-」ボタンクリック時に呼ばれる decrement () { console.log('Call the methods decrement') this.$store.commit('counter/decrement') } } } </script>store/counter.js
// カウンターの値を管理するストア (Vuex.Store) export const state = () => ({ count: 0 }) export const mutations = { increment (state) { console.log('Call the mutations increment') state.count++ }, decrement (state) { console.log('Call the mutations decrement') state.count-- } }Node.js サーバを起動
package.json に記述したライブラリをインストール。
$ npm installNode.js サーバを起動。
$ ./node_modules/nuxt/bin/nuxt.js ╭─────────────────────────────────────────────╮ │ │ │ Nuxt.js v2.12.1 │ │ Running in development mode (universal) │ │ │ │ Listening on: http://localhost:3000/ │ │ │ ╰─────────────────────────────────────────────╯ ℹ Preparing project for development ℹ Initial build may take a while ✔ Builder initialized ✔ Nuxt files generated ✔ Client Compiled successfully in 5.69s ✔ Server Compiled successfully in 5.30s ℹ Waiting for file changes ℹ Memory usage: 121 MB (RSS: 200 MB) ℹ Listening on: http://localhost:3000/Web ブラウザで http://localhost:3000/counter にアクセスすると「+」「-」ボタンと数値が増減するカウンターが表示される。
参考資料
- 投稿日:2020-03-27T20:47:40+09:00
多次元リスト(配列)を一次元に直す
空のリスト(配列)を新しく作成し、元の要素を一つずつコピーする。
pythonの場合
test.py# coding: utf-8 animal_list = ["イヌ", ["ペルシャ", "マンチカン", "スコティッシュフォールド"], "ヒト", ["ハムスター, カピバラ"]]; new_list = [] for animal in animal_list: if type(animal) == list: new_list.extend(animal) else: new_list.append(animal) print(new_list)
js(gas)の場合
test.gsfunction make_array() { var array = ["イヌ", ["ペルシャ", "マンチカン", "スコティッシュフォールド"], "ヒト", ["ハムスター, カピバラ"]]; var new_array = []; for (var i = 0; i <= array.length - 1; i++) { if (typeof(array[i]) == "object") { array[i].map(function(text) { new_array.push(text) }); } else { new_array.push(array[i]); } } Logger.log(new_array); }
<メモ>
・リストは可変(大きさを決める必要がない)に対し、配列は不変。(宣言時に大きさを決める必要がある)
・jsでは配列の大きさを決める必要がない?
- 投稿日:2020-03-27T20:47:28+09:00
Google Places API のデータで営業中か判定するアルゴリズム
Places API を使えば、Google Map に登録されているデータを取得できます。
Places API は取得したいデータに応じて API が複数ありますが、Place Details で fields にopening_hours
を指定すれば、そのお店の開店・閉店時間を取得できます。営業時間が次のお店なら、
- 日: 11:30 AM – 2:00 PM, 5:00 – 10:00 PM
- 月: 11:30 AM – 2:00 PM, 5:00 – 10:00 PM
- 火: 11:30 AM – 2:00 PM, 5:00 – 10:00 PM
- 水: 閉店
- 木: 11:30 AM – 2:00 PM, 5:00 – 10:00 PM
- 金: 11:30 AM – 2:00 PM, 5:00 – 10:00 PM
- 土: 11:30 AM – 2:00 PM, 5:00 – 10:00 PM
API で取得できるデータは次の通りです。なかなか複雑ですね!
{ "periods": [ { "close": { "day": 0, "time": "1400" }, "open": { "day": 0, "time": "1130" } }, { "close": { "day": 0, "time": "2200" }, "open": { "day": 0, "time": "1700" } }, { "close": { "day": 1, "time": "1400" }, "open": { "day": 1, "time": "1130" } }, { "close": { "day": 1, "time": "2200" }, "open": { "day": 1, "time": "1700" } }, { "close": { "day": 2, "time": "1400" }, "open": { "day": 2, "time": "1130" } }, { "close": { "day": 2, "time": "2200" }, "open": { "day": 2, "time": "1700" } }, { "close": { "day": 4, "time": "1400" }, "open": { "day": 4, "time": "1130" } }, { "close": { "day": 4, "time": "2200" }, "open": { "day": 4, "time": "1700" } }, { "close": { "day": 5, "time": "1400" }, "open": { "day": 5, "time": "1130" } }, { "close": { "day": 5, "time": "2200" }, "open": { "day": 5, "time": "1700" } }, { "close": { "day": 6, "time": "1400" }, "open": { "day": 6, "time": "1130" } }, { "close": { "day": 6, "time": "2200" }, "open": { "day": 6, "time": "1700" } } ] }このデータを使って、店舗が営業中かどうかを判定するアルゴリズムは次のようになります。
/** * 営業中か判定する * @param {Date} d 日付オブジェクト * @param {object} openingHours 営業時間のデータ * @return {boolean} 営業中なら true、閉店なら false */ const isOpen = (d, openingHours) => { const day = d.getDay(); const h = String(d.getHours()).padStart(2, '0'); const m = String(d.getMinutes()).padStart(2, '0'); const time = h + m; return openingHours.periods.filter(p => p.open.day === day).some(p => { return p.open.time <= time && time <= p.close.time; }); };コーディング例を https://codepen.io/takatama/pen/vYOvEQP に置きました。
- 投稿日:2020-03-27T20:07:13+09:00
Vue.js + Vuex によるカウンターのサンプルコード
概要
- Vue.js + Vuex を使用して「+」「-」ボタンで数値が増減するカウンターを作成する
今回の環境
- Vue.js 2.6.1
- Vuex 3.1.3
Vuex とは
Vuex は Vue.js アプリケーションのための 状態管理パターン + ライブラリです。 これは予測可能な方法によってのみ状態の変異を行うというルールを保証し、アプリケーション内の全てのコンポーネントのための集中型のストアとして機能します。
Vuex は 単一ステートツリー (single state tree) を使います。つまり、この単一なオブジェクトはアプリケーションレベルの状態が全て含まれており、"信頼できる唯一の情報源 (single source of truth)" として機能します。これは、通常、アプリケーションごとに1つしかストアは持たないことを意味します。
Vuex アプリケーションの中心にあるものはストアです。"ストア" は、基本的にアプリケーションの 状態(state) を保持するコンテナです。単純なグローバルオブジェクトとの違いが 2つあります。
Vuex ストアはリアクティブです。Vue コンポーネントがストアから状態を取り出すとき、もしストアの状態が変化したら、ストアはリアクティブかつ効率的に更新を行います。
ストアの状態を直接変更することはできません。明示的にミューテーションをコミットすることによってのみ、ストアの状態を変更します。これによって、全ての状態の変更について追跡可能な記録を残すことが保証され、ツールでのアプリケーションの動作の理解を助けます。
Vue.js + Vuex によるカウンターのサンプルコード
以下の HTML を Web ブラウザで読み込むと「+」「-」ボタンと数値が増減するカウンターが表示される。
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Vue.js + Vuex ぽちぽちカウンターサンプル</title> </head> <body> <div id="app"> <p>{{ count }}</p> <p> <button @click="increment">+</button> <button @click="decrement">-</button> </p> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.6.11"></script> <script src="https://unpkg.com/vuex@3.1.3/dist/vuex.js"></script> <script> // カウンターの値を管理するストア const store = new Vuex.Store({ state: { count: 0 }, mutations: { // increment: state => state.count++ increment() { console.log('Call the mutations increment') this.state.count++ }, // decrement: state => state.count-- decrement() { console.log('Call the mutations decrement') this.state.count-- } } }) new Vue({ el: '#app', computed: { count () { return store.state.count } }, methods: { // 「+」ボタンクリック時に呼ばれる increment() { console.log('Call the methods increment') store.commit('increment') }, // 「-」ボタンクリック時に呼ばれる decrement() { console.log('Call the methods decrement') store.commit('decrement') } } }) </script> </body> </html>参考資料
- 投稿日:2020-03-27T19:31:29+09:00
Sign in with Apple JSのSafariとそれ以外のブラウザの挙動の違い
はじめに
Sign in with Apple JSの動作を試している中で気づいた、Safariとそれ以外のブラウザ(Chromeなど)の挙動の違いについて結構ハマったので記事に残します。
同じ問題で困っている人の助けになれば幸いです。
SafariはMacにしか搭載されていないはずなので、この記事はMacOSが対象になります。知っておくべきこと
Safariは任意のパラメータの値が間違っていても、Safariが良い感じに調整してくれて動作するので、WebでSign in With Appleのボタンを設置する場合の動作確認は(動作対象であれば)Chromeでもやった方が良いです。
背景
Sign in with Appleのパラメータを確認するためにWebで簡易ログイン画面を作成し、動作確認をしていました。Safariだと成功しますが、Chromeだと
invalid_request
と以下のエラーメッセージが表示され、どのパラメータに問題があるのかがわからない状態で右往左往してました。
Application is not authorized to access the requested information.
余談: WebでSign in with Appleを使うために必要な手順
以下のブログが一番わかりやすかったです。heckがどういう意味なのかはわかっていない
https://developer.okta.com/blog/2019/06/04/what-the-heck-is-sign-in-with-applehttps://developer.apple.com でSign in With Appleを使うためにServiceIDを登録する必要があるのですが、ServiceIDを登録するためにはAppIDを登録し、そこでSign in With Appleを有効化して、さらにKeyを登録する必要があります。
公式のドキュメントにもよく読むと記載があるのですが、別ページに分かれておりJSだけで実装したい人でもちゃんとアプリの方も読む必要があります。自分はハマりました。Sign in With Apple for the webのドキュメント
https://help.apple.com/developer-account/?lang=en#/dev1c0e25352実際には一つ上のこれも読むとちゃんと書いてある
https://help.apple.com/developer-account/?lang=en#/dev77c875b7eドキュメントは全部読みましょう
挙動の違い
Safari
Sign in With Apple
ボタンをクリックするとポップアップが表示され、パスワードなりtouch barなりでログインを行います。任意のパラメータのvalidateは行われず、ログイン成功するとusePopup
のパラメータに従いリダイレクトページに遷移するかそのままかの処理が行われます。
(失敗させて遷移させる方法は思いつきませんでした)
また、usePopup
の値にかかわらずログインはポップアップで行われます。なのでログインが完了するまでは別の画面に遷移することはありません。Chrome
Sign in With Apple
ボタンをクリックするとログインページにパラメータを全て付与して遷移します。その際、invalidなパラメータがあるとエラー画面が表示されます。
usePopup
の値に従い、trueの場合は別ウィンドウを表示してログインを行い、falseの場合は元ページをログイン画面に遷移させ、ログイン処理を行います。ログイン完了後にリダイレクトするのはどちらも同じです。ちなみにエラーの原因
単純なケアレスミスで、
scope
パラメータでname
と必須パラメータと任意パラメータ
最後に
パラメータミスっても動作するSafari(実際にはしてなかった)はさすがのサポートですね。
今ではもうscope
パラメータを指定すればname
と
- 投稿日:2020-03-27T18:56:06+09:00
画像を添付するとその画像を表示するプログラムの作成方法
今回は、画像を添付するとその添付画像を表示するプログラムの作成方法をまとめていきたいと思います。
今回制作するのは、以下のような機能です。
事前準備
まず、以下のようにディレクトリーとファイルと適当な画像の入ったディレクトリー(この画像ではimage-changeという名前)を任意の場所に作成しておきます。
HTMLの記述
index.html<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <script type="text/javascript" src="js/script.js"></script> <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script> <link rel="stylesheet" type="text/css" href="css/layout.css"> </head> <body> <output id="output-image"></output> <p>※画像を選択してください</p><br> <input type="file" id="image-field" onchange="outputReadFile()"> </body> </html>CSSの記述
layout.cssimg{ width:200px; }Javascriptの記述
script.jsfunction outputReadFile() { var file = event.target.files[0]; // 添付ファイルのデータを変数fileに格納 if(file){ //ファイルが添付されている場合 initializeFile(); //初期化 var outputData = new FileReader; // FileReaderを作成 outputData.readAsDataURL(file); // ファイルの読み込み outputData.onload = outputFile(); // ファイルの読み取りを終えたタイミングでoutputFile関数を実行 function outputFile(){ // 添付画像のhtmlを作成 return function(event) { var img = document.createElement('img'); //img要素を生成 img.src = event.target.result; //添付画像の場所を指定 var outputImage = document.getElementById('output-image'); // outputタグのidを読み込み outputImage.insertBefore(img, null); // outputタグの中に添付画像のimgタグを出力 $('p').remove(); // p要素の"※画像を選択してください"を削除 }; }; }else{ //ファイルが添付されていない場合 initializeFile(); //初期化 var p = document.createElement('p'); // p要素生成 p.innerHTML="※画像を選択してください" // p要素の内容を追加 var outputImage = document.getElementById('output-image'); //outputタグのidを取得 outputImage.after(p); //outputタグの後にp要素を追加 } function initializeFile(){ var outputImage = document.getElementById('output-image'); //outputタグのidを取得 outputImage.innerHTML = ''; //outputタグの中身を削除 }; };最後に
HTMLとCSSを自分でカスタマイズすると、色々と(例えば、アプリ内のプロフィール画像の編集時や記事の投稿時などで)応用できるので便利です。
何か質問などあればコメントからどうぞ。
- 投稿日:2020-03-27T18:32:52+09:00
受託Webサイト制作の時の開発環境
今回の開発環境の備忘録と次点として生かそうと思い投稿しておきます。
前提
案件
ボリューム:20ページくらい
納期:2週間くらい
備考:レスポンシブ(SP版のデザインはなく良い感じに)業務フロー
コーディング→ローカル確認→テスト環境アップ(WebサーバにFTPソフトで)
開発環境(マシン、サーバ)
MacOS
MAMP開発リソース
php
css
js
assets(image, font, etc)コンパイル
Macのシェルスクリプトでコピーやsassを走らせて成果物を作る。
具体的には、CPコマンドやインストールしたsassコマンドでsrcディレクトリからdistディレクトリに吐き出していく。watch機能もつけた。ディレクトリ構成
site/ - dist/ - index.php(home) - _section.php(home) - detail/(page)...etc - css - home.css...etc - js - home.js...etc - common - header.php - footer.php...etc - src/ - assets/ - js/ - common/ - lib/ - bxSlider.js...etc - page/ - home - scss/ - common/ - page/ - home - style.scss - _section.scss...etc - template/ - common/ - page/ - home/ - index.php - _section.php...etc - compile.sh - watch.sh考察
まずコンパイルについては、
sass以外は、ほとんどコピーなので、コンパイルする必要性がなかった。
わかりやすくするためにcss以外のリソースも一緒にコンパイルしていたが、最初から必要はなかった。そのためコンパイルに余計な時間が取られたと思う。
ただ、個人的には開発リソースと成果物は分けるべきとは思っているので、今回のコンパイルは悪くないと思っている。
実際、jsの圧縮やphpテンプレートに自動でリソースパスを渡すなどの機能もつけやすくなると思っている。
一つ悪いポイントは、納期がパツパツ過ぎたため、余計なこと考える余裕もないし、実装する時間もないため、余計な機能になってしまったことだと思う。
実務ではそこも重要なポイントであるので、設計はほんとむずいと改めて思った。phpは、受託制作に関しては優秀であると再認識した。
正直特にphpの機能を使いこなした訳ではないが、includeと変数だけで、テンプレートを細分化できるので良い。1ページでだいたい処理が完結する(ページ単位でのスコープで良い)ので十分と思っています。sassはここで言うまでもなくとても良い。コンパイルで変更したファイルだけを見れるようにできたら良いと思った。なんかありそうな気はしてるが。
jsはほぼプラグイン。圧縮するほどの量もないが、がっつりした案件が来た時改め考えたい。もしできるなら、webpackを使ってcssModulesを使いたいのだが、そもそもWebサイト受託でそこまでやることあるかまだ疑問に思っているので保留。
次点
受託制作は、スピード重視かつ壊しやすい設計も必要なので、コンパイルはsassのみで、phpはそのまま編集が良いかなと思いました。
あと、スピード重視のため、cssはflexをめっちゃ多用すべきと思いました。
flexのポリフィルを入れればIE8までは対応できるので、今の時代なら大丈夫な範囲だと思ってます。
あとは、スライダーやスライドショーでbxSlider。まとめ
- コンパイルはsassだけで良い
- phpとcssのflexはスピード開発に良い
- bxSliderとflexibility.js
大して参考になるお話ではないと思いますが、アプリ開発とサイト制作の違いがあり、どちらにも効率的なやり方があるのだなーと思ったので、残した記事でした。
- 投稿日:2020-03-27T18:16:53+09:00
フロントでfirestoreのエラーを検知したい
とあるプロジェクトにて。
こんなかんじでfirestoreのエラー検知をしようとしているコードがありました。try { connectFirestore(); } catch { window.alert('firebase接続失敗!'); } try { watchFirestoreUpdate(); } catch { window.alert('firebase参照失敗!'); } try { updateFirestore(); } catch { window.alert('firebase更新失敗!'); }しかし、意図的にエラーを起こして動作確認したところ、、、
検知できてませんでした。
この記事は、firestoreのエラー検知をちゃんとやろうとした、格闘の記録です。
firestoreは使い慣れていないため、変なことをしていたらご指摘いただけるとありがたいです。
前提
- 言語はtsです
- フロントエンドです
- firebaseのバージョンは7です
- firestoreをつかって、別システムと連携をするページです
- 監視や更新がひっそりと失敗するのは、要件上クリティカルにまずいです
アウトライン
やろうとしたことの一覧と、結果を記載します。
やろうとしたこと 結果 何をした? 初回接続エラーの検知 できた setTimeout()で接続タイムアウトを検知 途中切断の検知 一応できたけど挙動が怪しいので導入見送り setInterval()で定期的に接続状態をチェック NW切断の検知 できた window.addEventListener('offline')を設定 参照エラーの検知 できた コールバックを使う 更新エラーの検知 できた コールバックを使う
エラー検知、その前に
以降のコードでは、↓を使います。
import firebase from 'firebase'; // 本記事の各所で使う汎用モジュール let singletonApp: firebase.app.App | null = null; export const getApp = (): firebase.app.App => { if (singletonApp === null) { // 初回だけinitialize (2回やるとduplicateエラーになる) singletonApp = firebase.initializeApp(/*引数省略*/); } return singletonApp; }; export const getRef = (path?: string): firebase.database.Reference => { return getApp().database().ref(path); };
初回接続エラーの検知
ページ読み込みの直後に行っている、認証や、firebase.database.Reference取得の失敗を検知できるようにしました。
認証エラーは素直にPromiseで検知できたのですが、reference取得エラーの検知が厄介でした。
接続失敗したときに各種イベントが発火するわけもないため、タイムアウトで検知するようにしました。// ロード時に実行する関数 // => 認証エラーと接続タイムアウトでalertを出す export const onLoad = (): void => { getApp() .auth() .signInWithEmailAndPassword('e-mail@gmail.com', 'password1234') .then(() => { console.log('firebase: authentication ok'); }) .catch((err) => { console.log(err); window.alert('firebase認証失敗') }); alertOnConnectionTimeout(); }; const alertOnConnectionTimeout = (): void => { // TIMEOUT_SEC秒経ってもfirebase接続ができなければalertを出す const TIMEOUT_SEC = 15; checkConnectionTimeout(TIMEOUT_SEC) .then(() => { console.log('firebase: connection ok'); }) .catch((err) => { console.log(err); window.alert('firebase接続失敗'); }); }; const checkConnectionTimeout = (timeoutSec: number): Promise<void> => { return new Promise((resolve, reject) => { getRef('.info/connected').on('value', (snapshot: any) => { // 接続したらここに来る if (snapshot.val() == false) { return; } resolve(); }); window.setTimeout(() => { // タイムアウトしたらここに来る reject(new Error('timeout')); }, timeoutSec * 1000); }); };
なお、以下はダメでした。// ダメだったケース export const onLoad = (): void => { checkConnectionOnce(); }; const checkConnectionOnce = (): void => { getRef('.info/connected').once('value', (snapshot: any) => { // 接続失敗なら、そもそもここに来ない if (snapshot.val() == false) { // 接続後、1回目のvalueイベントでここにくる (=正常系でアラートが出てしまう) window.alert('Firebaseの接続に失敗しました'); return; } // 接続後、2回目のvalueイベントでここにくる (=onceだとここに到達しない) // => よって、↑ではonceではなくonで見ることにした console.log('firebase: connection ok'); }); };
途中切断の検知
自分 <-(a)-> firebase <-(b)-> 他人 のうち、(a)の途中切断を検知しようとしました。
一応、以下のコードで検知できるとは思うのですが、
- devtoolで確認したところメモリリークしているっぽい挙動だった
- 次項のNW切断エラーを入れれば大半のケースで検知できるし、シンプルになる
という理由から、導入はやめました。
(NW切断の検知だけだと、NW正常でfirebase側に障害があった場合は検知できません。とはいえ参照・更新のタイミングでエラー検知できるため、大過ないだろうと考えました)// ロード時に実行する関数 export const onLoad = (): void => { // INTERVAL_SEC秒ごとに、firebaseの接続状態をチェック const INTERVAL_SEC = 30; setInterval( checkConnectionTimeout, // 中身は前項を参照 INTERVAL_SEC * 1000 ); };なお、onDisconnectといういかにも今回のケースに使えそうな名前の関数がありましたが、これは使えませんでした。
// onDisconnect()を使うと、 // 切断時に、firebase側でfirestoreの値を更新してくれる。 // ただ、値を更新をするためのインターフェースしか用意されていないため、 // window.alert()を出す、みたいなことには使えない。 getRef('my/app/path/is_connected').onDisconnect().set(false); /* 余談 * onDisconnect()による値の更新には罠があります。 * タブを閉じた場合は、即時値が反映されますが、 * NW切断の場合は、3分程たたないと値が反映されません。 * NW切断も含めてリアルタイムに値を反映したい場合は、何かしら別の手段が必要です。 */
NW切断の検知
firebaseではなく、ブラウザの機能をつかいました。超簡単。
window.addEventListener('offline', () => { window.alert('ネットワーク切断'); }, false);
参照エラーの検知
Referenceを使う場合、promiseではなく、なつかしのコールバックを使ってエラーを検知します。この仕様に気づかずそこそこハマりました。
(改めて型定義を見返すまで気づけなかったです。。。盲点でした。)const watchMyAppPath = () => { getRef('my/app/path').on('value', (snapshot) => { console.dir(snapshot.val()); }, captureWatchError); }; const captureWatchError = (err: any) => { if (err) { console.log(err); window.alert('firebase参照失敗'); } };なお、試してませんが、以下を使えば普通にtry/catchできるかもしれません。
- promisifyを使ってpromise化する
- ThenableReferenceを使ってみる
(名前から想像するに、thenやcatchを使えるようにしたReferenceと思われます。ドキュメントをみても使い方が分からなかったのと、変更の影響がでかそうだったため導入を見送りました)
更新エラーの検知
参照エラーと同じく、コールバックを使いました。
const updateMyAppPath = () => { getRef('my/app/path/boolean').set(true, captureWatchError); }; const captureUpdateError = (err: any) => { if (err) { console.log(err); window.alert('firebase更新失敗'); } };
まとめ
- 異常系もちゃんと動作確認をしよう
- .info/connectedを見ればfirebaseの接続状態が分かる
- window.addEventListener('offline') でNW切断を検知できる
- onDisconnect()でfirebase切断時に値を更新できるが、NW切断だとラグがあるので注意
- firestoreの参照、更新エラーにはコールバック関数を使う
以上です。
- 投稿日:2020-03-27T17:13:46+09:00
chromeで401レスポンスが返ってきたときの不具合
背景
https://www.nnn.ed.nico/questions/14833
n予備校の教材の通りに実装を進めていたところ、このリンクと同じ状況に遭遇問題
chromeに限り、以下のコードで表示されるはずのログアウトしましたという文とリンクが表示されない。
function handleLogout(req, res) { res.writeHead(401, { 'Content-Type': 'text/html; charset=utf-8' }); res.end('<!DOCTYPE html><html lang="ja"><body>' + '<h1>ログアウトしました</h1>' + '<a href="/posts">ログイン</a>' + '</body></html>' ); }原因
chromeのバグ
https://bugs.chromium.org/p/chromium/issues/detail?id=992639解決
- とりあえず別のステータスコードを変える
function handleLogout(req, res) { console.log('handleLogout'); res.writeHead(400, { 'Content-Type': 'text/html; charset=utf-8' }); res.end('<!DOCTYPE html>'+ '<html lang="ja"><body>' + '<h1>ログアウトしました</h1>' + '<a href="/posts">ログイン</a>' + '</body></html>'); }
- (ブラウザを変える)
追記
教材にこのエラーについての解決方法が追記されていた。
解決方法は、ステータスコードを302に書き換えること。https://developer.mozilla.org/ja/docs/Web/HTTP/Status
より、302 Found
このレスポンスコードは、リクエストされたリソースの URI が一時的に変更されたことを示します。
- 投稿日:2020-03-27T17:06:12+09:00
【Nuxt.js】Components番外編: 親子間のやりとりを$emit, props, Vuexで見てみよう
前置き
コンポーネント間のやりとりについて、
やりたいことがある時に
パターンがいくつかあるよという例です?
近々Vuexに関する記事を投稿していたので
そのパターンも含めて書いてみました✍️
それぞれどんな時に使うかも
目次ごとに記載しています?
・emit, $event
・propsをfunc
・ Vuexpropsでfuncすることは
あまりないのですが…!
初めから誰もが見やすい
完璧なコードを目指すのではなく、
動かすだけならこんな方法もある!
と知ることは大事だと思います???それぞれ基礎編の記事はこちら
emit基礎編
https://note.com/aliz/n/nd6e771724cd7
props基礎編
https://note.com/aliz/n/n99144d4556b9
vuex基礎編
https://note.com/aliz/n/n497914c981c8やりたいこと
buttonで名前を変えるだけです笑
とにかくシンプルにわかりやすく!
がモットーです?
表示はこちらです。
・親ではname
・子ではmyNamefilecomponents/ --| Name.vue pages/ --| index.vue store/ --| index.jsパターン1: $emit, $event
【どんな時に使う?】
今回のように構造がシンプルな時⭕️
というか基本はコレです!✨
親で渡しているpropsの値を変えます。
これができればモーダルウィンドウも簡単!
https://note.com/aliz/n/n2f0bc857defbName.vue<template> <div class="button"> <p>myName: {{ myName }}</p> <button @click="changeName"> reset </button> </div> </template> <script> export default { props: { myName: { type: String, required: false }, }, methods: { changeName() { this.myName = "Max" this.$emit('nameSwitch', this.myName) // または1行にまとめて this.$emit('nameSwitch', 'Max') } }, } </script>【index.vue】
@nameSwitch="name = $event"
親にあるdataのname: 'Bob'を
イベントで操作します?
@nameSwitch="name"にすると
通常methodsのname()になるけど
dataのnameをイベントで渡すことにより
子のmethods, resetNameを使用します!$emitカスタムイベント
https://jp.vuejs.org/v2/guide/components-custom-events.htmlindex.vue<template> <div class="page"> <p>name: {{ name }}</p> <Name :myName="name" @nameSwitch="name = $event" /> </div> </template> <script> import Name from '~/components/Name.vue' export default { components: { Name }, data () { return { name: "Bob" } }, } </script>親にmethods追加して
名前を戻してみましょう。
ちゃんと戻りますね?
index.vue<template> <div class="page"> <p>name: {{ name }}</p> <Name :myName="name" @nameSwitch="name = $event" /> <button @click="resetName"> reset </button> </div> </template> <script> import Name from '~/components/Name.vue' export default { components: { Name }, data () { return { name: "Bob" } }, methods: { resetName () { this.name = "Bob" }, }, } </script>エラー解消は
子で値を変えなければOKです!
基礎編をご覧ください??パターン2: propsをfunc
【どんな時に使う?】
あんまり使いません。
前置きに書いたように
動かす手段の1つとしてご紹介。【特徴】
コードは単純で分かりやすいです?
ただこれをやるなら
$emitやvueの方が良いですね⭕️
子と親でのやりとりを
理解するのには最もシンプルで
良いかもしれません?こちらが参考になります!
https://kuroeveryday.blogspot.com/2017/09/vuejs-callback-vs-emit-events.htmlName.vue<template> <div class="button"> <p>myName: {{ myName }}</p> <button @click="resetFn"> resetFn </button> </div> </template> <script> export default { props: { myName: { type: String, required: false }, resetFn: { type: Function }, }, } </script>index.vue<template> <div class="page"> <p>name: {{ name }}</p> <Name :myName="name" :resetFn = "resetName" /> </div> </template> <script> import Name from '~/components/Name.vue' export default { components: { Name }, data () { return { name: "Bob" } }, methods: { resetName () { this.name = "Max" }, }, } </script>パターン3: vuex
【どんな時に使う?】
ネストが深すぎてpropsがどうなってるか
分かりにくい!!って時に使います。
あとはサーバーとの通信が基本なので
今回のシンプルかつ通信不要な際は使いません。ただ導入が容易ではなく
気軽にオススメはしません?♂️?
本当に必要な時には⭕️【特徴】
一見、コードもファイルも増えて
ごちゃついてそうな印象ですが☁️
何がどこにあって、が超絶見やすいですね✨?Name.vue<template> <div class="button"> <p>myName: {{ myName }}</p> </div> </template> <script> export default { props: { myName: { type: String, required: false }, }, } </script>index.vue<template> <div class="page"> <p>name: {{ name }}</p> <Name :myName="name" @nameWasReset="name = $event" /> <button @click="$store.commit('changeName')"> change </button> </div> </template> <script> import Name from '~/components/Name.vue' export default { components: { Name }, computed: { name () { return this.$store.getters.name }, }, } </script>index.jsexport const state = () => ({ name: "Max", }) export const getters = { name(state) { return state.name }, } export const mutations = { changeName(state) { state.name = "Bob" }, }記事が公開したときにわかる様に、
note・Twitterフォローをお願いします?
https://twitter.com/aLizlab
- 投稿日:2020-03-27T16:47:11+09:00
Chart.jsにいい感じの色を自動的に割り当てる
やりたいこと
簡単にカッコいいグラフやチャートを表示できるJavascriptライブラリ「Chart.js」を使う際、色を自分で指定するのが大変。更にデータの個数が可変な時、自動的にいい感じの色を割り当てたい。
実現方法
Chart.jsのプラグイン「chartjs-plugin-colorschemes」を使うとプリセットされたいい感じのカラースキームを使うことができます。
chartjs-plugin-colorschemes
https://nagix.github.io/chartjs-plugin-colorschemes/ja/およそ500種類くらいのカラースキームが用意してあるので好みのものを選んでください。
今回はReactを使った例で説明していきます。更に「Chart.js」をそのまま使うのではなくのReactラッパーである「react-chartjs-2」を使っていきます。データをpropsで渡すだけでコンポーネントのように簡単にグラフやチャートを追加できます。
手順
まずはnpmを使ってインストールしていきます。
$ npm install --save chart.js react-chartjs-2 chartjs-plugin-colorschemes追加したいjsファイルの先頭でモジュールのインポートをしていきましょう
//今回はドーナッツグラフを作ります import { Doughnut } from 'react-chartjs-2'; //一括でインポートしても... import 'chartjs-plugin-colorschemes'; //ファイルサイズを減らしたい場合はカラースキームだけをインポートしても大丈夫です //今回はbrewr.Paired12というカラースキームを使いたいので以下のように指定しました。 import { brewr } from 'chartjs-plugin-colorschemes/src/colorshemes/colorschemes.office';Reactのファイルはこんな感じになります。
家計簿アプリを例に説明すると、今回はAPIを叩いてカテゴリごとの金額の合計を取得してセットしています。import React, { useState, useEffect } from 'react'; import { Doughnut } from 'react-chartjs-2'; import { brewer } from 'chartjs-plugin-colorschemes'; import axios from 'axios'; const DoughnutChart = (props) => { const [chartData, setChartData] = useState({}); useEffect(() => { getChartData(); }, []); const getChartData = () => { axios .get("/api/getChartData") .then(res => { const expenses = res.data; let labels = []; let data = []; expenses.forEach(expense => { labels.push(expense.category); data.push(expense.money); }); setChartData({ labels:labels, datasets: [ { label: labels, data: data, //ここはfalseに設定 fill: false, } ] } ); }); }; const options = { plugin: { colorschemes: { scheme: 'brewr.Paired12' } } }; return ( <React.Fragment> <Doughnut data={chartData} options={options}/> </React.Fragment> ); }; export default DoughnutChart;このコンポーネントを組み込んで表示するとこんな感じのおしゃれな配色のグラフが表示されます。
もし新たなカテゴリのデータが登録され、表示するデータが増えた際もカラースキームから色を割り振って表示してくれます。今回使っている「brewr.Paired12」の場合は12色まで対応可能です。
参考にさせて頂いたサイト
Chart.js | Open source HTML5 Charts for your website
chartjs-plugin-colorschemes
jerairrest/react-chartjs-2: React wrapper for Chart.js - GitHub
react-chartjs-2とChart.jsを使ってグラフを作ってみた - Qiita
- 投稿日:2020-03-27T16:24:06+09:00
Javascript基本集(3)~関数~
Javascript基本集(3)~関数~
自分の学習用です
基本書式function 関数名(引数) { 内部処理 return 戻り値; }例題
function hanbun(hikisuu) { var xxx = hikisuu / 2; return xxx; }関数名・引数
『hanbun』というのが『関数名』。『hanbun』のあとの括弧内に書かれた『hikisuu』が、『引数』(ひきすう)と呼ばれる変数になります。『引数』は、内部処理を行う際に、外部から値を受け取るための変数。
『引数』を書く括弧の中には、変数名を書く。
引数が必要ないのなら、何も書かなくてもOK。『引数』を複数使いたい場合は『hanbun(hikisuu1, hikisuu2)』のように『,』(カンマ)で区切って並べて書く。
宣言した『引数』は、『関数』内でのみ有効。return 戻り値
最後の行の『return xxx;』について。
『return』は、「ここで『関数』を終了する」という宣言。
『関数』内で『return』の書かれた行が実行されると、その場所で『関数』の処理が終了する。
『関数』が終了すると、その『関数』が呼び出された場所まで処理が戻る。
『関数』は、『{}』の末尾まで来るか、『return』の行まで来ると、処理を終え、呼び出された場所に戻る。今回はここまで
- 投稿日:2020-03-27T16:22:49+09:00
Vue.js・Nuxt.jsに入門したときにみたリンク集
はじめに
Nuxt.jsでとあるサイト(HPぐらいのレベル感で状態管理とかはほとんどないシンプルなアプリケーション)を構築することになりVueに入門しました。
Nuxt.jsでSPAでの構築、UIフレームワークはVuetifyです。
筆者は、Reactの経験はありますが、Vueの経験はありません。
そのため、公式系をざざっと読み、Nuxtでアプリを構築しながら必要なことを調べながら実装しました。
そのときに調べたことのリンク集です。だいたいこれで理解した。
公式系
- Vue.js
- Nuxt.js
- Vuetify
- Vuex。Vueの状態管理FW
- アイコンをみつけるまでに時間かかった...最近のデフォはここらしい。違うサイトをみていたりしたので最新のVueでマテリアルアイコンが表示されない現象に遭遇しましたが、アイコン名の指定が違うだけでした。。mdiのprefixつけても表示されずに無駄にハマりました...
Nuxt
Vue
- ライフサイクルフック
- 省略記法。:hrefとか、@clickとかなんぞや
- methodsとcomputedの違い
- これは算出プロパティを理解すればOK
- v-forのkeyについて
- 指定していなかったらconsoleで怒られた
- レスポンシブ対応のために画面サイズを取得する
- methodsでHTMLを返却してレンダリングしたい
- コンポーネント間の値の受け渡し
Deploy
- 投稿日:2020-03-27T16:03:03+09:00
javascript
- 投稿日:2020-03-27T15:28:14+09:00
フロントエンドの速度改善でやったこと
最近、Vue.jsで作られたWebページの速度改善をやりました。
対応内容について書き残します。前置き
- ページ読み込み速度ではなく、読み込んだ後の速度を改善しました
- 対応内容は、Vue.jsに限らず、フロント全般で有効な内容です
- バックエンドの速度改善は含まれません
概要
No 何をした? どうやった? 1 API呼び出しの並列化 Promise.all()を使う 2 大量データの一覧表示が重くならないようにする virtual-scrollを使う 対応1: API呼び出しの並列化
before
async mounted() { // 4つのAPIを順次実行 const response1 = await apiRequest1(); const response2 = await apiRequest2(); const response3 = await apiRequest3(); const response4 = await apiRequest4(); },mounted()内で複数のAPIを順次実行している箇所がありました。
当たり前の話ですが、この場合
処理時間 = api1の実行時間 + api2の実行時間 + api3の実行時間 + api4の実行時間
となるため、遅いです。
幸い今回はapiの実行順序を守る必要がなかったため、次のように並列化しました。after
async mounted() { // 4つのAPIを並列実行 const [ response1, response2, response3, response4, ] = await Promise.all([ apiRequest1(), apiRequest2(), apiRequest3(), apiRequest4(), ]); },これで、
処理時間 = api1〜4のうち一番遅いものの実行時間
となり、速くなりました。なお、細かいエラー処理をしたい場合は次のようにもできます。
async mounted() { const [ response1, response2, response3, response4, ] = await Promise.all([ apiRequest1().catch((err) => { console.log(`apiRequest1() failed: err(${err})`); }), apiRequest2().catch((err) => { console.log(`apiRequest2() failed: err(${err})`); }), apiRequest3().catch((err) => { console.log(`apiRequest3() failed: err(${err})`); }), apiRequest4().catch((err) => { console.log(`apiRequest4() failed: err(${err})`); }), ]); console.log('Promise.all finished!'); // 例えばapiRequest4()だけ失敗した場合は、 // apiRequest4() failed: err(エラー内容) // Promise.all finished! // が出る。 // response4はundefinedになる。 },対応2: 大量データの一覧表示が重くならないようにする
about
データの一覧を表示している箇所について、データ数が多くなると、動作がもっさりする問題がありました。
対策として、virtual-scrollを導入しました。virtual-scrollとは、ユーザーに見える部分だけDOMを生成することで、擬似的にスクロールを実現する仕組みのことを指します。
例えば1万件データがあった場合、
普通にv-forでlist表示をすると、1万個のDOMが描画されます。
一方、virtual-scrollでlist表示すると、ユーザに見える分+α(サイズにもよるけどせいぜい数十個)のDOM描画で済みます。参考: https://kakkoyakakko2.hatenablog.com/entry/2018/10/23/003000
(ユーザに見える部分だけDOMを生成する様子がgifアニメになっており、わかりやすいです)sample
各種フレームワークにvirtual-scroll用のライブラリがあるようですが、今回はvueを使っているため、
vue-virtual-scroll-listを使いました。
以下、サンプルコードと、実行結果です。<template> <div class="container"> <h1>Qiita Test</h1> <!-- 普通にv-forで回すと、データ量が多いときの描画コストが大きい --> <h2>Before: normal list</h2> <ul style="height: 200px; overflow: scroll;"> <li style="list-style: none;" v-for="user in users" :key="user.id" > {{ user.name }} </li> </ul> <!-- virtual-scrollを使うと、表示される分だけ描写するため、描画コストがデータ量に依存しない --> <h2>After: virtual scroll list</h2> <ul style="height: 200px; overflow: scroll;"> <vue-virtual-scroll-list :size="20" :remain="15" > <li v-for="user in users" :key="user.id" > {{ user.name }} </li> </vue-virtual-scroll-list> </ul> </div> </template> <script lang="ts"> import vue from 'vue'; import VueVirtualScrollList from 'vue-virtual-scroll-list'; export default vue.extend({ components: { VueVirtualScrollList, }, data() { return { users: [] as Array<{id: number, name: string}>, }; }, async mounted() { this.users = [ {id: 1, name: 'user1'}, {id: 2, name: 'user2'}, {id: 3, name: 'user3'}, {id: 4, name: 'user4'}, // 後略。大量のユーザーがいるとします。 ]; }, }); </script>最後に
virtual-scrollは同僚に教えてもらいました。
スクロールするとDOMがゴリゴリ変わっていくのは、なかなかにお洒落。
5, 6秒かかってた処理が一瞬になったときの気持ちよさったらなかったです。
- 投稿日:2020-03-27T14:17:37+09:00
ファイルを DATA URI に変換するフォーム
ファイルを DATA URI に変換するフォーム
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>DATA URI 変換</title> <meta name="viewport" content="width=device-width, initial-scale=1"> </head> <body> <input type="file" id="fileform"> <br> <br> <textarea id="datauri" cols="100" rows="30"></textarea> <br> <button type="button" id="copybutton">コピー</button> <script> const toBase64DataUri = file => new Promise(resolve => { const reader = new FileReader(); reader.onload = () => resolve(reader.result); reader.readAsDataURL(file); }); document.getElementById("fileform").addEventListener("input", async (e) => { const file = e.target.files[0]; const datauri = await toBase64DataUri(file); document.getElementById("datauri").value = datauri; }, false); document.getElementById("copybutton").addEventListener("click", () => { document.getElementById("datauri").select(); document.execCommand("copy"); }, false); </script> </body> </html>ファイルを DATA URI に変換してる部分
const toBase64DataUri = file => new Promise(resolve => { const reader = new FileReader(); reader.onload = () => resolve(reader.result); reader.readAsDataURL(file); });ファイルを DATA URI に変換してる部分を呼び出している部分
const datauri = await toBase64DataUri(file);
- 投稿日:2020-03-27T14:16:25+09:00
【随時更新】日本コロナMAPを作ります。【解説用】
最初に
最近プログラミングを始めたお医者さんの友達が、
コロナウイルスの患者数が県ごとにわかるMAPのWEBアプリを作りたい!
ということだったので、説明のために、挫折0を目指し、ソフトのダウンロードから説明していきます。
最小構成で考えるため、DBは使いません。
作業環境は、Windows10。Visual Studio Codeのダウンロードとインストール
まずは、フロントエンドのコードを書くエディタが必要です。
フロントエンドとは、Html,Css,Javascriptで構成されるWEBページのことです。
・Htmlがページに表示される要素の構成するコード
・CSSがページデザインの設定するコード
・Javascriptが要素に動きをつけるコード
最初は、この3点からフロントをコード化して、
最後にPythonのFlaskからサーバー化→Herokuからサービス化という流れで行こうと思います。さっそくVisualStudioCode(https://code.visualstudio.com/ )をダウンロード!
1.「Download for Windows」からダウンロードする。
2.ダウンロードした「VSCodeUserSetup-x64-1.43.2.exe」を実行
3.インストーラーが開くので「同意する」→「次へ」→「次へ」
→追加タスクの選択では、一応デスクトップ上にアイコンを作成する にチェックつけときましょう。
頻繁に使うんでね。最後、「インストール」を選択。終わったら「完了」でソフトが自動で起動します。
こんな画面になります。最後にメニューのFileからAutoSaveにチェックをいれておきましょう。
VisualStudioCodeからディレクトリ構成を構築する(動作GIFあり)
1.メニューバーのFileから、OpenFolderを選択。
2.好きなディレクトリに移動し、そこで右クリック→フォルダを新規作成(coronaMap)→そのフォルダを選択する
3.次に、map.htmlファイルの作成とjs/myscript.js, css/mystyle.cssフォルダの作成を行います。【下のGIF画像参照】
ディレクトリ構成は、以下のようになっているはずです。├── coronaMap └── css | └── mystyle.css ├── js | └── myscript.js map.html4.次は、Map情報を描画するためにJQueryとJapanMapをダウンロードします!
まずは,JQueryから、VisualStudioCodeのjsディレクトリ内に、jquery.3.4.1.min.jsというファイルを作ります。
こちらからページをCtrl+Aで全選択しコピーし(https://code.jquery.com/jquery-3.4.1.min.js )、
貼り付けます。【GIF参照】
次に、JapanMapをダウンロードします。
このサイト(https://takemaru-hirai.github.io/japan-map/ )のZIPをクリックし、ダウンロードしてください。
ダウンロードしたZIPファイルを解凍し、中にある「jquery.japan-map.min.js」をコピーし、
今回のcoronaMap/jsフォルダの中に貼り付けてください。
準備は整った。あとはゴリゴリとコードを書きます。(コピペOk)
まずは、map.htmlの記述から
map.html<!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <title>日本MAP</title> <!-- JQueryとJapanMapの指定 --> <script src="js/jquery.3.4.1.min.js"></script> <script src="js/jquery.japan-map.min.js"></script> <!-- 自分で記述していくJavascriptファイルの指定 --> <script src="js/myscript.js"></script> <!-- 自分で記述していくCSSファイルの指定 --> <link href="css/mystyle.css" rel="stylesheet"/> </head> <body> <!-- MAPを記述していくコンテナ --> <div id="map-container"></div> </body> </html>次は、Javascriptの記述です。
基本的に自分が既出していくJavascriptは、myscript.jsのみです。myscript.js$(function(){ let areas = [ {code : 1, name: "北海道", color: "#7f7eda", hoverColor: "#b3b2ee", prefectures: [1]}, {code : 2, name: "青森", color: "#759ef4", hoverColor: "#98b9ff", prefectures: [2]}, {code : 3, name: "岩手", color: "#759ef4", hoverColor: "#98b9ff", prefectures: [3]}, {code : 4, name: "宮城", color: "#759ef4", hoverColor: "#98b9ff", prefectures: [4]}, {code : 5, name: "秋田", color: "#759ef4", hoverColor: "#98b9ff", prefectures: [5]}, {code : 6, name: "山形", color: "#759ef4", hoverColor: "#98b9ff", prefectures: [6]}, {code : 7, name: "福島", color: "#759ef4", hoverColor: "#98b9ff", prefectures: [7]}, {code : 8, name: "茨城", color: "#7ecfea", hoverColor: "#b7e5f4", prefectures: [8]}, {code : 9, name: "栃木", color: "#7ecfea", hoverColor: "#b7e5f4", prefectures: [9]}, {code : 10, name: "群馬", color: "#7ecfea", hoverColor: "#b7e5f4", prefectures: [10]}, {code : 11, name: "埼玉", color: "#7ecfea", hoverColor: "#b7e5f4", prefectures: [11]}, {code : 12, name: "千葉", color: "#7ecfea", hoverColor: "#b7e5f4", prefectures: [12]}, {code : 13, name: "東京", color: "#7ecfea", hoverColor: "#b7e5f4", prefectures: [13]}, {code : 14, name: "神奈川", color: "#7ecfea", hoverColor: "#b7e5f4", prefectures: [14]}, {code : 15, name: "新潟", color: "#7cdc92", hoverColor: "#aceebb", prefectures: [15]}, {code : 16, name: "富山", color: "#7cdc92", hoverColor: "#aceebb", prefectures: [16]}, {code : 17, name: "石川", color: "#7cdc92", hoverColor: "#aceebb", prefectures: [17]}, {code : 18, name: "福井", color: "#7cdc92", hoverColor: "#aceebb", prefectures: [18]}, {code : 19, name: "山梨", color: "#7cdc92", hoverColor: "#aceebb", prefectures: [19]}, {code : 20, name: "長野", color: "#7cdc92", hoverColor: "#aceebb", prefectures: [20]}, {code : 21, name: "岐阜", color: "#7cdc92", hoverColor: "#aceebb", prefectures: [21]}, {code : 22, name: "静岡", color: "#7cdc92", hoverColor: "#aceebb", prefectures: [22]}, {code : 23, name: "愛知", color: "#7cdc92", hoverColor: "#aceebb", prefectures: [23]}, {code : 24, name: "三重", color: "#ffe966", hoverColor: "#fff19c", prefectures: [24]}, {code : 25, name: "滋賀", color: "#ffe966", hoverColor: "#fff19c", prefectures: [25]}, {code : 26, name: "京都", color: "#ffe966", hoverColor: "#fff19c", prefectures: [26]}, {code : 27, name: "大阪", color: "#ffe966", hoverColor: "#fff19c", prefectures: [27]}, {code : 28, name: "兵庫", color: "#ffe966", hoverColor: "#fff19c", prefectures: [28]}, {code : 29, name: "奈良", color: "#ffe966", hoverColor: "#fff19c", prefectures: [29]}, {code : 30, name: "和歌山", color: "#ffe966", hoverColor: "#fff19c", prefectures: [30]}, {code : 31, name: "鳥取", color: "#ffcc66", hoverColor: "#ffe0a3", prefectures: [31]}, {code : 32, name: "島根", color: "#ffcc66", hoverColor: "#ffe0a3", prefectures: [32]}, {code : 33, name: "岡山", color: "#ffcc66", hoverColor: "#ffe0a3", prefectures: [33]}, {code : 34, name: "広島", color: "#ffcc66", hoverColor: "#ffe0a3", prefectures: [34]}, {code : 35, name: "山口", color: "#ffcc66", hoverColor: "#ffe0a3", prefectures: [35]}, {code : 36, name: "徳島", color: "#fb9466", hoverColor: "#ffbb9c", prefectures: [36]}, {code : 37, name: "香川", color: "#fb9466", hoverColor: "#ffbb9c", prefectures: [37]}, {code : 38, name: "愛媛", color: "#fb9466", hoverColor: "#ffbb9c", prefectures: [38]}, {code : 39, name: "高知", color: "#fb9466", hoverColor: "#ffbb9c", prefectures: [39]}, {code : 40, name: "福岡", color: "#ff9999", hoverColor: "#ffbdbd", prefectures: [40]}, {code : 41, name: "佐賀", color: "#ff9999", hoverColor: "#ffbdbd", prefectures: [41]}, {code : 42, name: "長崎", color: "#ff9999", hoverColor: "#ffbdbd", prefectures: [42]}, {code : 43, name: "熊本", color: "#ff9999", hoverColor: "#ffbdbd", prefectures: [43]}, {code : 44, name: "大分", color: "#ff9999", hoverColor: "#ffbdbd", prefectures: [44]}, {code : 45, name: "宮崎", color: "#ff9999", hoverColor: "#ffbdbd", prefectures: [45]}, {code : 46, name: "鹿児島", color: "#ff9999", hoverColor: "#ffbdbd", prefectures: [46]}, {code : 47, name: "沖縄", color: "#eb98ff", hoverColor: "#f5c9ff", prefectures: [47]}, ]; const getWidth = $(window).width();//画面の大きさ取得 $("#map-container").japanMap({ width: getWidth,//画面の大きさそのまま描画する selection: "area", areas: areas, backgroundColor : "#dcdcdc", borderLineColor: "#f2fcff", borderLineWidth : 0.25, lineColor : "#a0a0a0", lineWidth: 1, drawsBoxLine: true, showsPrefectureName: true, prefectureNameType: "short", movesIslands : true, fontSize : 11, fontShadowColor : "#fff", onSelect : function(data){ alert(data.name); } }); });ここまで出来たら、いったん確認してみます。
map.htmlファイルをドラッグ&ドロップでWEBブラウザ上で動かす
いったん休憩で、次は、いよいよコロナの情報を取得してMAPに反映させます。
Pythonを扱う準備
Pythonで行う役割は主に2つです。
サーバー化とスクレイピングです。
まずは最初に、サーバー化からはじめましょう。
今回は、小規模なのでFlaskを利用します。1.pycharmで先ほどの作業ディレクトリを開きます。(File→Open→先ほどのcoronaMapを選択)
※Pycharmとanacondaはインストール済みで話を進めます。
次に、作業用で利用するpython環境をanacondaで構築しましょう。
windowsアプリからanaconda promptを起動する。
この画面で、環境を作成conda create -n corona python=3.6.8 conda activate coronaconda createの分で
Proceed ([y]/n)?
とでたら、「y」と入力し、Enter次は、Flaskのインストール
pip install flaskここまでできたら、pycharmの設定にこのPython環境を以下の流れで適応させます。(GIFあり)
「File」→「Settings」→「Project:coronaMap」→「Project Interpreter」
→Project Interpreter欄の一番右にある歯車ボタン→Add→Existing environmentを選択し、
Interpreterの欄の一番右の「...」を選択する。
anaconda→envs→corona→python.exeを選択
この手順で、先ほど作成したPython環境に適応できます。
Flaskでサーバー化
先ほど作成したmap.htmlとjs, cssファイルの構成を変えます。
新しくtemplatesフォルダとstaticフォルダを作成し、map.htmlをtemplatesフォルダの中に、
js,cssフォルダをstaticフォルダの中にぶち込みます。
そして、main.pyを作成する。以下が変更されたディレクトリ構成
├── coronaMap └── templates | └── map.html ├── static | ├── jsフォルダ | └── cssフォルダ main.pymain.pyにコードを記述
main.pyfrom flask import * app = Flask(__name__) server_mode = "local" @app.route("/", methods=["GET", "POST"]) def corona_page(): return render_template("map.html") if __name__ == "__main__": if server_mode == "local": port = 5000 app.run(debug=True, port=port)これで、Runから起動してみると、http://127.0.0.1:5000/ にアクセスすると同様のMAPが表示されるはずです。
表示が正しくされない場合は、map.htmlのscriptのsrc先が"../static/js/jquery.3.4.1.min.js"のようにstaticのパスが通っているか確認してください。とりあえずもう一回休憩。
次は、コロナウイルスの情報をスクレイピングしてきます。pythonでスクレイピングしてMAPに反映
スクレイピングするサイトは、厚生労働省のページ!信用度高い
https://www.mhlw.go.jp/stf/newpage_10465.htmlまずは、前回と同じようにanaconda promptで必要になるモジュールをインストール
pip install requests pip install bs4新しいPythonファイル(scraping_corona)を作成して記述します
scraping_corona.pyimport requests from bs4 import BeautifulSoup #探すサイト( --https://www.mhlw.go.jp/stf/houdou/houdou_list_202003.html-- ) # 新型コロナウイルス感染症の現在の状況と厚生労働省の対応について(令和2年3月26日版) url = 'https://www.mhlw.go.jp/stf/newpage_10465.html' prefectures_directory = { '北海道':1, '青森県':2, '岩手県':3, '宮城県':4, '秋田県':5, '山形県':6, '福島県':7, '茨城県':8, '栃木県':9, '群馬県':10, '埼玉県':11, '千葉県':12, '東京都':13, '神奈川県':14, '新潟県':15, '富山県':16, '石川県':17, '福井県':18, '山梨県':19, '長野県':20, '岐阜県':21, '静岡県':22, '愛知県':23, '三重県':24, '滋賀県':25, '京都府':26, '大阪府':27, '兵庫県':28, '奈良県':29, '和歌山県':30, '鳥取県':31, '島根県':32, '岡山県':33, '広島県':34, '山口県':35, '徳島県':36, '香川県':37, '愛媛県':38, '高知県':39, '福岡県':40, '佐賀県':41, '長崎県':42, '熊本県':43, '大分県':44, '宮崎県':45, '鹿児島県':46, '沖縄県':47 } def get_colona_data(): r = requests.get(url) if r.status_code == requests.codes.ok: soup = BeautifulSoup(r.content, 'html.parser') data = [[[td.get_text(strip=True) for td in trs.select('th, td')] for trs in tables.select('tr')] for tables in soup.select('table')] #1行目がアナウンス・対策情報 announce_data = data[0] #2行目が国別の感染者数のデータ country_corona_data = data[1] #3行目が国内の感染者数データ ['都道府県名', '患者数', '現在は入院等', '退院者', '死亡者'] japan_corona_data = data[2] japan_corona_data_true = []#都道府県のみのデータ max_patient_num = max([int(japan_data[1]) for japan_data in japan_corona_data[1:-1]]) def hex_color_change(patient_num): ratio = patient_num / max_patient_num * 255 hex_data = hex(int(ratio))[2:] if len(hex_data) == 1: str_hex_data = "#0"+str(hex_data) + "0000" else: str_hex_data = "#" + str(hex_data) + "0000" return str_hex_data for japan_data in japan_corona_data[1:-1]: prefectures = japan_data[0] code_num = prefectures_directory[prefectures] code_id = "pref_"+str(code_num) patient_num = int(japan_data[1]) hospitalized_num = int(japan_data[2]) end_hospitalized_num = int(japan_data[3]) dead_num = int(japan_data[4]) hex_data = hex_color_change(patient_num) jd = { "prefectures": prefectures, "code_num:": code_num, "code_id": code_id, "patient_num": patient_num, "hospitalized_num": hospitalized_num, "end_hospitalized_num": end_hospitalized_num, "dead_num": dead_num, "hex_data": hex_data } japan_corona_data_true.append(jd) return japan_corona_data_true #確認用 #jcd = get_colona_data() #print(jcd)出力を確認する場合は、一番下のコメントアウトを消してください。
マップを表示する前に、
このコードを通して、スクレイピングして情報をmap.htmlに渡します。それに伴い,main.pyも編集
main.pyfrom flask import * from scraping_corona import get_colona_data#追加 app = Flask(__name__) server_mode = "local" @app.route("/", methods=["GET", "POST"]) def corona_page(): japan_corona_data = get_colona_data()#追加 return render_template("map.html", japan_corona_data=japan_corona_data) if __name__ == "__main__": if server_mode == "local": port = 5000 app.run(debug=True, port=port)次に、HTMLを記述していきます。
HTMLにデータを格納→Javascriptに受け渡しをします。map.html<!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <title>日本MAP</title> <!-- JQueryとJapanMapの指定 --> <script src="../static/js/jquery.3.4.1.min.js"></script> <script src="../static/js/jquery.japan-map.min.js"></script> <!-- 自分で記述していくJavascriptファイルの指定 --> <script src="../static/js/myscript.js"></script> <!-- 自分で記述していくCSSファイルの指定 --> <link href="../static/css/mystyle.css" rel="stylesheet"/> </head> <body> <!-- MAPを記述していくコンテナ --> <div id="map-container"></div> <!-- データを格納エリア --> <div id="japan_corona_data"> {% for data in japan_corona_data %} <div class="corona-data" id="{{ data.code_id }}" data-prefectures="{{ data.prefectures }}" data-code_num="{{ data.code_num }}" data-patient_num="{{ data.patient_num }}" data-hex_data="{{ data.hex_data }}"></div> {% endfor %} </div> </body> </html>次は、Javascriptでの受け取り、MAPに反映させます。
myscript.js$(function(){ let areas_data_list = []; for (let index=0; index < 47; index++){ const select_code = index+1; const select_id = "#pref_"+select_code.toString(); let areas_data; if ($(select_id).length){ areas_data = { code: select_code, name: $(select_id).data('prefectures'), color: $(select_id).data('hex_data'), hoverColor: $(select_id).data('hex_data'), prefectures: [select_code], } } else{ areas_data = { code: select_code, name: "", color: "#000000", hoverColor: "#000000", prefectures: [select_code], } } areas_data_list.push(areas_data); } const getWidth = $(window).width();//画面の大きさ取得 $("#map-container").japanMap({ width: getWidth,//画面の大きさそのまま描画する selection: "area", areas: areas_data_list, backgroundColor : "#222222",//背景を黒くする borderLineColor: "#f2fcff", borderLineWidth : 0.25, lineColor : "#a0a0a0", lineWidth: 1, drawsBoxLine: true, showsPrefectureName: false,//件名を非表示にする prefectureNameType: "short", movesIslands : true, fontSize : 11, fontShadowColor : "#fff", onSelect : function(data){ alert(data.name); } }); });再び、Runでmain.pyを実行し、http://127.0.0.1:5000/
こんな画像が表示されました。感染者数が多ければ多いほど赤くなります。
またまた少し休憩します。
次は、マウスを当てた状態で、グラフを表示するようにします。
- 投稿日:2020-03-27T14:05:30+09:00
【JavaScript】2次元のベクトルクラス【HTML5】
はじめに
自分用の2次元のベクトルクラスです。
ES6で書いています。静的クラスとして書いています。ソース
// ベクトルクラス(ベクトルの計算に使用) class Vector { // 足し算 static add(v0, v1) { return { x: v0.x + v1.x, y: v0.y + v1.y, }; } // 引き算 static subtract(v0, v1) { return { x: v0.x - v1.x, y: v0.y - v1.y, }; } // ベクトルの長さを返す static length(v) { return Math.sqrt(v.x * v.x + v.y * v.y); } // 単位ベクトルを返す(非破壊的) static unit(v) { const len = Vector.length(v); return { x: v.x / len, y: v.y / len }; } // 内積 static innerProduct(v0, v1) { return v0.x * v1.x + v0.y + v1.y; } }
- 投稿日:2020-03-27T14:00:39+09:00
JavaScriptで特定の要素が配列に含まれるか判定する
JavaScriptにin演算子があったので、SQLみたいに以下のように書けるかと思ったのですが、うまくいきませんでした?
const arr = ['a', 'b', 'c']; console.log('a' in arr); // falseになってしまったどうやらJavaScriptのin演算子は「要素」が含まれるかどうかのチェックではなく、「プロパティ」が含まれるかどうかをチェックするみたいです?
ですので、以下のような感じが正しい使い方
(arr.lengthは要素の数を返すプロパティ)const arr = ['a', 'b', 'c']; console.log('length' in arr); // trueになる今回求めたい、特定の要素が含まれるかどうか、というのはindexOfもしくはincludesで判定できます?
const arr = ['a', 'b', 'c']; console.log(arr.indexOf('a') !== -1); // trueになる console.log(arr.includes('a')); // trueになる
- 投稿日:2020-03-27T13:58:44+09:00
【JavaScript】3x3の行列クラス【HTML5】
はじめに
自分用の3x3の行列クラスです。
ES6で書いています。静的クラスとして書いています。ソース
// 行列クラス(行列の計算に使用) class Matrix { // m0は行列、m1は行列又はベクトル // 行列は大きさ9の1次元配列であること。 ex. [ 1, 0, 0, 0, 1, 0, 0, 0, 1 ] // ベクトルはxとyをプロパティに持つ連想配列であること。 ex. { x: 2, y: 4 } // 左からベクトルをかけることは想定していない static multiply(m0, m1) { if(m1.length && m1.length === 9) {// m1は行列 return [ m0[0] * m1[0] + m0[1] * m1[3] + m0[2] * m1[6], m0[0] * m1[1] + m0[1] * m1[4] + m0[2] * m1[7], m0[0] * m1[2] + m0[1] * m1[5] + m0[2] * m1[8], m0[3] * m1[0] + m0[4] * m1[3] + m0[5] * m1[6], m0[3] * m1[1] + m0[4] * m1[4] + m0[5] * m1[7], m0[3] * m1[2] + m0[4] * m1[5] + m0[5] * m1[8], m0[6] * m1[0] + m0[7] * m1[3] + m0[8] * m1[6], m0[6] * m1[1] + m0[7] * m1[4] + m0[8] * m1[7], m0[6] * m1[2] + m0[7] * m1[5] + m0[8] * m1[8], ]; } else {// m1はベクトル return { x: m0[0] * m1.x + m0[1] * m1.y + m0[2], y: m0[3] * m1.x + m0[4] * m1.y + m0[5], }; } } // 単位行列 static identify() { return [1, 0, 0, 0, 1, 0, 0, 0, 1]; } // 平行移動行列 static translate(x, y) { return [1, 0, x, 0, 1, y, 0, 0, 1]; } // 拡大縮小行列 static scale(x, y) { return [x, 0, 0, 0, y, 0, 0, 0, 1]; } // 回転行列 static rotate(theta) { const cos = Math.cos(theta), sin = Math.sin(theta); return [cos, -sin, 0, sin, cos, 0, 0, 0, 1]; } // 逆行列を求める static inverse(m) { const det = Matrix.determinant(m), inv = [ m[4] * m[8] - m[5] * m[7], -(m[1] * m[8] - m[2] * m[7]), m[1] * m[5] - m[2] * m[4], -(m[3] * m[8] - m[5] * m[6]), m[0] * m[8] - m[2] * m[6], -(m[0] * m[5] - m[2] * m[3]), m[3] * m[7] - m[4] * m[6], -(m[0] * m[7] - m[1] * m[6]), m[0] * m[4] - m[1] * m[3] ]; return inv.map(elm => elm / det); } // 行列式を求める static determinant(m) { return m[0] * m[4] * m[8] + m[1] * m[5] * m[6] + m[2] * m[3] * m[7] - m[2] * m[4] * m[6] - m[1] * m[3] * m[8] - m[0] * m[5] * m[7]; } }
- 投稿日:2020-03-27T12:56:38+09:00
Jestの比較メソッドについて
リファクタリングを行ってる時にJavaScriptのテストの比較周りの知識が曖昧だったのでまとめます。
調べるきっかけとなったコード
is.test.tsit("Jest's test", () => { .... assert.deepStrictEqual(value, [{ id: 1, name: "aaaaa" }]); });JestでAssertionがエラーとなった。
is.test.tsassert.deepStrictEqual(received, expected) Expected value to deeply and strictly equal to: [{"id": 1, "name": "aaaaa"}] Received: [{"id": 1, "name": "aaaaa"}] Difference: Compared values have no visual difference.
assert.deepStrictEqual
は値の完全一致と思い込んでいたので、なんで失敗するのだろう?と少しはまりました。Jestの比較
Jestにも同様の関数が存在するので試した結果が以下。
is.test.tsclass LaCroix { public flavor: any; constructor(flavor: any) { this.flavor = flavor; } } it("同一の確認", () => { // "レモン" === "レモン" -> 成功 expect("レモン").toBe("レモン"); // true === 1 -> 失敗 expect(true).toBe(1); // 1 == true -> 成功 expect(1).toBeTruthy(); // LaCroix { // "flavor": "レモン", // } // Object { // "flavor": "レモン", // } // 値が同じなので成功 expect(new LaCroix("レモン")).toEqual({flavor: "レモン"}); // LaCroix { // "flavor": 1, // } // Object { // "flavor": "1", // } // 値が違うので失敗 expect(new LaCroix(1)).toEqual({flavor: '1'}); // LaCroix { // "flavor": "レモン", // } // Object { // "flavor": "レモン", // } // 値は同じだがオブジェクトの型が違うので失敗 expect(new LaCroix("レモン")).toStrictEqual({flavor: "レモン"}); // LaCroix { // "flavor": "レモン", // } // LaCroix { // "flavor": "レモン", // } // 値もオブジェクトの型も同じなので成功 expect(new LaCroix("レモン")).toStrictEqual(new LaCroix("レモン")); // LaCroix { // "flavor": "レモン", // } // Object { // "flavor": "レモン", // } // 値は同じだがオブジェクトの型が違うので失敗 assert.deepStrictEqual(new LaCroix("レモン"), {flavor: "レモン"}); // LaCroix { // "flavor": 1, // } // LaCroix { // "flavor": "1", // } // 値もオブジェクトの型も同じなので成功 assert.deepStrictEqual(new LaCroix("レモン"), new LaCroix("レモン")); });名前的に
=== (厳格な等価性比較)
とtoStrictEqual
が同じものと思っており、プリミティブ値に使ってたことがあります。
toBeTruthy
よりtoBe(true)
のほうが厳密なテストですね。まとめ
プリミティブ値の比較を行う場合は
toBe
を使う。
オブジェクトの値の比較を行う場合はtoEqual
を使う。
オブジェクトの値、型の比較を行う場合はtoStrictEqual
を使う。(assert.deepStrictEqualでも可
)参考
- 投稿日:2020-03-27T12:54:51+09:00
GAとGTMを駆使してA/Bテストする方法
こんな方向け
- 検証期間を同じにして、A/Bテストを行いたい
- 対象となるGoogleアナリティクスのビューでUser ID を有効にしているため、Google オプティマイズが使えない
やれること
- ボタンのA/Bテスト
- ファーストビューのA/Bテスト
など
前提条件
- Googleアナリティクスが導入済みであること
- GTMが導入済みであること
やりかた
手順は4つです。
1. A/Bテストしたい部分のHTMLを、テストパターンの個数ぶん書く
2. ランダムで出しわける部分、HTMLを書き替える部分をjQueryで書く
3. 書いたjQueryをGTMに登録する
4. GTMでGoogleアナリティクスにイベントを送信するここでは、ボタンのA/Bテストを想定して、例を紹介します
以降は、下記ボタンがページにある想定で説明を進めます。
<div id="abcd-button"> <a target="_blank" href="ページURL"> <img src="ボタン画像パス.png"> </a> </div>1.A/Bテストしたい部分のHTMLを、テストパターン数分だけ書く
下記2つを書き分けたHTMLを用意してください
- idの値
- ボタン画像
テストパターンAのHTML
<div> <a target="_blank" href="ページURL" id="abcd-button-abtest-a" class="abcd-button-abtest"> <img src="Aパターンのボタン画像パス.png"> </a> </div>テストパターンBのHTML
<div> <a target="_blank" href="ページURL" id="abcd-button-abtest-b" class="abcd-button-abtest"> <img src="Bパターンのボタン画像パス.png"> </a> </div>2.ランダムで出しわける部分、HTMLを書き替える部分をjQueryで書く
<script> $(function() { //手順1で作成したテストパターンのHTMLを変数に代入します var htmlA = ''; var htmlB = ''; //テストパターンのHTMLを代入した変数を配列にします var testList = [htmlA,htmlB] //テストパターンの中からランダムで1つ選びます var quantity = testList.length; while (quantity) { var j = Math.floor( Math.random() * quantity ); var t = testList[--quantity]; testList[quantity] = testList[j]; testList[j] = t; } //spliceの値は右の値をパターン数から1個引いた値を入れてください //今回は2パターンのテストなので、2-1=1となり、1になります testList.splice(1,1); var abtest = testList.join(''); //オリジナルのボタンのID名を値に入れます var $content = $('#abcd-button'); //ランダムで1つ選んだものを、オリジナルと差し替えます $content.replaceWith(abtest); }); </script>3.書いたjQueryをGTMに登録する
下記手順で、GTMに新規タグを登録します。
- タグ新規作成
- タグタイプで「カスタムHTML」を選択
- HTMLの入力欄に、手順2で作成したスクリプトを貼り付ける
- トリガーを新規作成
- トリガータイプで「ページビュー」を選択
- トリガーの発生場所で「一部のページビュー」を選択
- 「Page URL」「等しい」を選択し、値はボタンがあるページのURLを入力
- トリガーを保存する
- タグを保存する
4.GTMでGoogleアナリティクスにイベントを送信する
下記手順で、GTMに新規タグを登録します。
- タグ新規作成
- タグタイプで「Google アナリティクス: ユニバーサル アナリティクス」を選択
- トラッキング タイプで「イベント」を選択
- カテゴリ、アクション、ラベルを任意で入力
- 値で「Click ID」を選択
- 詳細設定>タグの順位付け>●●が発効する前にタグを配信にチェックを入れる
- 手順3で作成したタグを選択
- トリガーを新規作成
- トリガータイプでクリック>「リンクのみ」を選択(「すべての要素」でも良い)
- 「Click Classes」「等しい」を選択し、値は手順1でリンク要素に記述したクラス名を入力(例ではabcd-button-abtest)
- トリガーを保存する
- タグを保存する
以上で設定終了です。
Googleアナリティクス>行動>イベント からクリック数を見て、A/Bどちらが良かったか、比較することができます。
- 投稿日:2020-03-27T11:43:50+09:00
jestでモックしてみたメモ
レガシーコードのテストをjestに移行していた時に、
jestだとどうやってモックしたり、追跡したりするんだ?となったのでその時のメモ最初に
相当アホなところでハマったのだが、jest はnamespaceをそのまま読み込むため以下のようの読み込まないといけない。
import 'jest' // OK import jest from "jest" // NG jest.xxxx()特定の関数をモックする
関数にもろもろについてモックの仕方です。
テスト用のモック関数の作成
const mockFunc = jest.fn().mockImplementation(() => return "mock func"); console.log(mockFunc()); // mock func関数自体のモックは jest.fn()に集約されており、単純に何か値を返すのみの関数は mockImplemantionの引数で定義します。
また、mock関数の引数や、どういった値を返したかの検証も行えます。関数の動作の検証
引数の検証
実際にモックした関数の引数を見たい場合はこんな感じでmock.calls を使います
const mockFunc = jest.fn().mockImplementation((message: string) => { return message; }); mockFunc('test1'); mockFunc('test2'); console.log(mockFunc.mock.calls); // [ ['test1'], ['test2']]戻り値の検証
戻り値の場合はmock.resultsを使います。
const mockFunc = jest.fn().mockImplementation((message: string) => { return message; }); mockFunc('test1'); mockFunc('test2'); console.log(mockFunc.mock.results); /* [ { type: 'return', value: 'test1', }, { type: 'return', value: 'test2', }, ]; */value は実際に返した値で、
typeはその値が正常リターンか、エラーかを判定します。正常の場合は
return
エラーがスローされた場合はthrow
となります。また、
imcomplete
というケースがありますが、これはよくわからないので省略します。後処理
テストの後処理は2種類あり、 関数のモック自体を抹消する場合と、関数の引数などの追跡結果を抹消する場合があります。
const mockFunc = jest.fn().mockImplementation((message: string) => { return message; }); mockFunc.mockClear() // どういった引数を受取、どういった値を返したかの計測結果をリセット mockFunc.mockReset() // 実際に実装した関数や戻り値の設定をリセット特定のオブジェクトに生えている関数を追跡、モックする
追跡対象を指定
const spy = jest.spyOn(targetObject , "do"); // オブジェクトの指定とそこから生えている関数を指定 // 関数実行処理 expect(spy).toHaveBeenCalled(); // 関数が実行されたかどうかを検証指定した関数をモック関数にする
const spy = jest.spyOn(targetObject , "do") .mockImplemention(() => "spy");↑のようにすると、指定したオブジェクトの関数は、mockImplementionの中で定義した関数に差し替えられます。
なお、mockImplementionの中には先程解説した jest.fnを渡すこともでき、その場合は引数などの検証も行えると思います(未検証)オマケ 現在時刻のモック
特定の時刻でポイントが失効するかどうかをチェックしたい場合に現在時刻をモックする必要があったのでその時のことも書いておきます。
重要なことは、日付を扱うパッケージオブジェクト(moment, dayjsなど)はjavascriptのDateオブジェクトに依存しているため、Dateオブジェクトをモックすれば現在時刻系は騙せます。
なので、
- jest で Dateオブジェクトをモックする
- mockDate を使う
- 自分でDateオブジェクトをモックする(レガシーコードはこれをやってた)
個人的に、mockDateを使うのが良いかなと思います。使いやすいしリストアなどもやりやすい他、
ドキュメントも充実しているので、情報共有もしやすいです。最後に
時刻関連のモックをしらべるのに一番時間がかかりました。
Dateオブジェクトをいじればいいみたいな結果は出てくるのですが、なぜそれが必要なのかという理由が納得できず、
momentの初期化処理のコードを見に行ってDateオブジェクトに依存しているとわかりました。それでは、良いjestライフを
- 投稿日:2020-03-27T11:24:40+09:00
Javascript基本集(2)~配列~
Javascript基本集(配列)
自分の学習用です
配列宣言
配列の宣言には2種類の方法がある。
① 配列コンストラクタ「Array」を使う方法
~ 「new」を使ってArray()の引数へ格納したい文字列を設定する ~② 配列リテラル「[ ]」を使う方法
~ []を使ってそのまま文字列を設定する ~//①配列コンストラクタの例 var array1 = new Array('いぬ', 'さる', 'きじ'); //②配列リテラルの例 var array2 = ['いぬ', 'さる', 'きじ']; console.log( array1 ); console.log( array2 );実行結果
["いぬ", "さる", "きじ"] ["いぬ", "さる", "きじ"]
コントラクタとリテラルでの相違点var array1 = new Array(5); var array2 = [5]; console.log( array1 ); console.log( array2 );実行結果
1. [empty × 5] //コントラクタ 2. 3. [5] //リテラル配列コンストラクタで「new Array(5)」と記述すると「5つ分の配列要素を用意する」という意味になる。
配列リテラルで「[5]」と記述すると「5という数値を配列に格納する」という意味になる。JavaScriptで配列を宣言(作成)する時は、特に理由がない限り「配列リテラル」を使うように
配列の初期化
var array = []; console.log( array );実行結果
[]
『配列』操作
例題
var a = ["aa","bb"]; var b = ["EE","FF"]; var c = [1,5,7,3];よく使う命令
式 意味 使い方 説明 indexOf() 配列の要素を検索 a.indexOf(1); 2番目の要素が表示される length 配列の要素の個数 x = a.length; 変数『x』に『2』が入る push(array) 配列を末尾に追加 a.push(b); 変数『a』が『"aa","bb","EE","FF"』になる push(値) 値を末尾に追加 a.push("cc"); 変数『a』が『"aa","bb","cc"』になる unshift(array) 配列を先頭に挿入 a.unshift(b); 変数『a』が『"EE","FF","aa","bb"』になる unshift(値) 値を先頭に挿入 a.unshift("cc"); 変数『a』が『"cc","aa","bb"』になる pop() 末尾の要素を削除 a.pop() 変数『a』が『"aa"』になる shift() 先頭の要素を削除 a.shift() 変数『a』が『"bb"』になる concat(array) 連結した配列 x = a.concat(b); 変数『x』に『"aa","bb","EE","FF"』が入る sort() 小さい順に並び替える c.sort(); 変数『c』が『1,3,5,7』になる reverse() 逆向きに並び替える c.reverse(); 変数『c』が『3,7,5,1』になる join("文字列") 配列を文字列で連結 x = a.join("/"); 変数『x』に『aa/bb』が入る toString() 配列を文字列にする x = a.toString(); 変数『x』に『aa,bb』が入る map 配列の要素一つ一つにコールバック関数を実行する c.map(function(value){return value * 2;}); 『2,10,14,6』となる filter 対象となるデータに特定の条件を与えて、それに該当するデータだけを抽出 c.filter(function(value){return value > 6;}) 『7』が抽出される mapについては、3つの引数を取得することができる
例えば、
配列データ.map( function( value, index, array ) { });・「value」は、配列の値
・「index」は、配列のインデックス番号
・「array」は、現在処理している配列引数の「index」を使って、偶数の「Index番号」の時だけ値を2倍にするパターン
var items = [1,2,3,4,5,6,7,8,9]; var result = items.map( function( value, index, array ) { //「index番号」が偶数の時だけ2倍にする if( index % 2 !== 0 ) { return value * 2; } else { return value; } }); console.log( result );実行結果
[1, 4, 3, 8, 5, 12, 7, 16, 9]「map」は元の配列データに一切変更を加えない特徴がありますが、3つ目の引数「array」を利用すれば、元のデータを変更することも可能
var items = [1,2,3,4,5,6,7,8,9]; items.map( function( value, index, array ) { //「array」と「index」を利用して、元の配列データを変更する array[index] = value * 2; }); //元の配列データを出力する console.log( items );実行結果
[2, 4, 6, 8, 10, 12, 14, 16, 18]第2引数のオブジェクトについて
第2引数に任意の「オブジェクト」を指定することが出来るvar array = [ 配列データ ]; //第2引数にオブジェクトを指定 array.map( コールバック関数, オブジェクト );例題(オブジェクトから任意のキーワードを指定して「価格」を返す)
var foodList = { 'オムライス': 450, '焼きそば': 500, 'お好み焼き': 600, '焼き飯': 400 }; //任意のキーワードを指定する var order = ['焼き飯', 'お好み焼き']; var result = order.map( function( value, index, array ) { //配列のキーワードを使ってオブジェクト内の値を取得する return this[value]; }, foodList ); console.log( result );実行結果
[400, 600]第2引数へ「foodList」と記述することで、コールバック関数内の「this」が「foodList」を参照できるようになる
今回はここまで
- 投稿日:2020-03-27T11:13:24+09:00
モーダル、SPのメニューが表示されたときだけ中の要素をスクロールさせる
モーダル、SPのメニューが表示されたときだけ中の要素をスクロールさせる
※覚書メモです
以前jQueryでモーダルやSPのメニューを表示したときに背景を固定する記事を載せたことがありました。
しかしそれから数年たって、そんなことをしなくても簡単に実装できちゃういいものを見つけることができました(教えてくださった同僚には感謝です)。body-scroll-lock
▽github
https://github.com/willmcpo/body-scroll-lock▽npmjs
https://www.npmjs.com/package/body-scroll-lock指定した要素だけスクロールができるようになるというライブラリ。
androidもiosも難なくできました。
まさにそれがしたかったこと…!
以下使い方
今回もyarnを使います。
yarnじゃなくてもnpmでも大丈夫です(インストールするときのかき方が違うので気を付けてください)yarn add body-scroll-lock例)開くとき
import { disableBodyScroll, clearAllBodyScrollLocks } from 'body-scroll-lock' // 例えばメニュー const openBtn = document.getElementsByClassName('openBtn')[0] const menuInner = document.getElementsByClassName('menu-inner')[0] // スクロールさせたい要素 openBtn.addEventListener('click' , () => { disableBodyScroll(menuInner) })分かりやすいように「開く」「閉じる」を分けましたが、上の例に続いて書いても大丈夫です。
例)閉じるとき
// 例えばメニュー const closeBtn = document.getElementsByClassName('closeBtn')[0] closeBtn.addEventListener('click' , () => { clearAllBodyScrollLocks() })この2つだけ。スクロールさせたい要素の指定を変更すればすぐに実装できます。
あんなに頭を悩ませていましたが、本当に簡単なので早く知りたかったです。。以上
- 投稿日:2020-03-27T09:54:49+09:00
(箱だけドラフト)【最短・脳死】 画像認識AI LINE BOTの作り方
■目次
•はじめに
•Line Botアカウントを作ろう
•使いたAIを見つけよう
•Javascriptコード(フュージョン!!!)
•Line botへの接続
•(参考)AIの作り方○はじめに
さて、できるだけ簡単に画像認識AI Botを作りましょう!
尚、AIと言ってもAIメーカーですので悪しからず。①Line botアカウントを作ろう!
■@n0bisukeさん記事
1時間でLINE BOTを作るハンズオン (資料+レポート) in Node学園祭2017 #nodefest - Qiita
-LINE developersのサイトにアクセスし、ログインしましょう。
-Messaging APIのチャンネルを作りましょう!
-Botと友達になろう(QRコード)②AIメーカーで画像認識AIを見つけよう。
AIメーカーにアクセスしてLine BotにしたいAIを見つけましょう!
-見つけたAIから①id(3~4桁)②APIキー③AIに登録されているラベルをメモ③Javascriptコード
鋭意作成中(すみません)④Line botへ接続しよう
■再び@n0bisukeさん記事
1時間でLINE BOTを作るハンズオン (資料+レポート) in Node学園祭2017 #nodefest - Qiitaへ
-npm/3000
-web hookを更新⑤実演
作成中(余談)AI botの作り方
タオアリス作ってみた=土屋太鳳と広瀬アリス見分けbot■終わりに
間に合わなくてすみません。この記事は捨てずに完成させます。
プロトアウト生の宿題を見て他のAPIを勉強します。