- 投稿日:2020-02-24T23:20:51+09:00
マネーフォワードからの出金お知らせをラインに通知する
やったこと
Google APP Scriptでマネーフォワードからのメールを定期取得
LINE Notify APIを使用してメール本文をLINEに通知
なぜやるのか
- 不正利用やサービスの解約し忘れで意図せず引き落とされてないか怖い
- できるだけリアルタイムに通知を受け取りたい
- 複数のクレジットカードを使用しているので、それぞれの明細を把握するのは手間
- Gmail見る頻度低いので日常的に目に触れるLINEに通知したい
なぜGoogle APP Scriptか
- マネーフォワードにLINE通知機能はない
- IFTTTはLINE通知できるが、Gmailをアクション元に設定できない
- ZapierはGmailをアクション元に設定できるが、LINE通知できない
--> 仕方ないのでGoogle APP Scriptで書く
本題
Gmail側の設定で送信元メールアドレスがマネーフォワードのメールにラベルを付ける
ラベルを付けるとlabel:"ラベル名"で検索できるようになります。LINE通知に必要なトークンを取得する
LINEに通知はLINE Notify APIを使用します。
以下の記事が分かりやすく説明されていました。
[超簡単]LINE notify を使ってみるGoogle APP Scriptで直近30分間に受信したラベルのついたメールを取得してLINEに通知
以下のコードを書く。
var lineToken = "ラインのトークン" var interval = 30 var label = "ラベル名(今回はmoney_forward)" function fetchMoneyForwardMail() { //取得間隔 var now = Math.floor(new Date().getTime() / 1000); var time = now - (60 * interval); var searchCond = '(label:' + label + ' after:' + time + ')'; var thread = GmailApp.search(searchCond); var messages = GmailApp.getMessagesForThreads(thread); var formattedMsgs = []; for (var i = 0; i < messages.length; i++) { formattedMsgs[i] = "\n【date】: " + messages[i].slice(-1)[0].getDate() + "\n【Subject】: " + messages[i].slice(-1)[0].getSubject() + "\n【Body】: \n" + messages[i].slice(-1)[0].getPlainBody().slice(0, 200); return formattedMsgs; } } function send_line(message) { var payload = { 'message': message }; var options = { "method": "post", "payload": payload, "headers": { "Authorization": "Bearer " + lineToken } }; UrlFetchApp.fetch("https://notify-api.line.me/api/notify", options); } function main() { newMessages = fetchMoneyForwardMail() if (newMessages && newMessages.length > 0 && newMessages.length < 50) { for (var i = newMessages.length - 1; i >= 0; i--) { Logger.log(newMessages[i]) send_line(newMessages[i]) } } }Google APP Scriptの時間ベースイベントで上記処理を30分に一度実行する
以下のように設定します。
GASのスクリプトを指定した時間に自動で実行する方法以上です!
- 投稿日:2020-02-24T22:36:11+09:00
同期的に複数のファイルをform-dataを使って送信するサンプル
こんにちは、wattak777です。
一ファイルを送信する、というサンプルは幾つかあるのですが、requestとform-dataを組み合わせて送る場合、複数ファイルの場合、例えばforループで送るようにしても1つ目、2つ目、3つ目と送りきる前にどんどん送ってしまうため同期的に送ることが出来ません。
なので、ちょっとサンプルを作ってみました。
サーバー側はmulterを使った以下のサンプル。
server.jsvar express = require( 'express' ) ; var app = express() ; var multer = require( 'multer' ) ; app.post('/file_upload', multer({dest: ファイルを置くパス}).single('my_file'), function (req, res) { console.log(req.file.path, req.file.originalname) ; res.sendStatus(200) ; }); var server = app.listen(12345, function() { console.log("listening at port %s", server.address().port) ; });で、本題のクライアント側は以下の実装。
request-promiseを使って同期的に送るようにしました。client.jsconst fs = require('fs') ; const request = require('request-promise') ; const FileNameList = [ 'test1.bin', 'test2.bin', 'test3.bin' ] ; var FileNameIndex = 0 ; var returnCode = httpPost() ; function httpPost() { const FormData = { my_file: { value: fs.createReadStream(ファイルのパス + FileNameList[FileNameIndex]), options: { filename: FileNameList[FileNameIndex], contentType: 'application/octet-stream' } } } const options = { uri: "http://サーバーのIPアドレス:12345/file_upload", formData: FormData, method: 'post', headers: { 'Content-Type': 'multipart/form-data' } } var response = request(options) .then( function(body) { console.log( 'then :' + body ) ; onEnd() ; }) .catch( function(err) { console.log( 'catch error :' + err ) ; }) ; return response.statusCode ; } function onEnd() { console.log( 'Index ' + FileNameIndex + ' is finish.' ) ; FileNameIndex = FileNameIndex + 1 ; if ( FileNameIndex >= FileNameList.length ) { console.log( 'End Operation.' ) ; } else { var res = httpPost() ; } } console.log( 'Start Operation.' ) ;とやると、クライアント側の表示は以下のようになります。
$ node client.js Start Operation. then :OK Index 0 is finish. then :OK Index 1 is finish. then :OK Index 2 is finish. End Operation.
- 投稿日:2020-02-24T22:31:20+09:00
Vue.jsで日本語入力中に未確定状態でinput boxにmaxlengthを超えた部分が表示されないようにするには
インプットボックスの文字数制限
最近、インプットボックスの文字数制限を実装する必要があり、日本語入力中の挙動で結構はまりました。とある方のアドバイスで解決したので、備忘録として残しておこうと思います。アドバイスしてくださった方、ありがとうございました。なお、今回はVue.jsでの実装で直面した問題でしたが、Vue.js以外でも(JavaScriptなら)同様のことが起こりうると思います。(より良い実装の情報があれば、ぜひ教えてください。)
シンプルに
maxlength
を指定した実装以下ソースコードで問題ないと思いきや...
ソースコード
html<div id=app> <input type="text" v-model="test" maxlength="5"> {{test}} </div>Vue.jsconst app = new Vue ({ el: '#app', data: { test:'' } })もし、挙動を試してみたい方は、以下codepenからお試しください。
See the Pen This form can't restrict max length on Japanese language input by Yasunori MATSUOKA (@YasunoriMATSUOKA) on CodePen.
微妙な状況に
しかし、この実装では日本語入力中に未確定状態では最大文字数の
maxlength
を超えた部分についてもインプットボックスに表示されてしまうという状況に悩まされていました。(Enter
押す等して確定すれば、maxlength
以降の文字は消えるのですが、その前の未変換の状態や変換中はmaxlength
以上の文字が入力できているような印象をユーザーに与えてしまうという点で微妙な状況でした。)解決策
inputイベント時に
event.target.value
の長さをチェックし、maxlength
を超えている場合はinput boxからフォーカスを外す(event.target.blur()
する)解決策のソースコード
html<div id=app> <input type="text" v-model="test" maxlength="5" @input="onInput"> {{test}} </div>Vue.jsconst app = new Vue ({ el: '#app', data: { test:'' }, methods: { onInput (event) { console.log('onInput', event) if (event.target.value.length > event.target.getAttribute("maxlength")) { event.target.blur() } } } })無理矢理感は否めませんが、これで、
maxlength
を超えた際は、自動的に対象のinput boxからフォーカスが外れ、日本語入力が強制的に確定され、maxlength
以降の仮入力中の文字がinput boxに表示されることが無くなりました。試してみたい方は、以下のcodepenでお試しください。
See the Pen Restrict max length on Japanese language input by Yasunori MATSUOKA (@YasunoriMATSUOKA) on CodePen.
試したがうまくいかなかったもの
keydown
イベント時にevent.target.value
の長さをチェックし、maxlength
を超えている場合はイベントを無効化するうまくいかなかった実装のソースコード
html<div id=app> <input type="text" v-model="test" maxlength="5" @keydown="onKeyDown"> {{test}} </div>VUe.jsconst app = new Vue ({ el: '#app', data: { test:'' }, methods: { onKeyDown (event) { console.log('onKeyDownEvent', event) if (event.target.value.length >= 5) { if (event.code !== "Backspace" && event.code !== "Delete" && event.code !== "ArrowRight" && event.code !== "ArrowLeft" && event.code !== "ArrowUp" && event.code !== "ArrowDown" && event.code !== "PageUp" && event.code !== "PageDown" && event.code !== "Enter" && event.code !== "Space" && event.code !== "Tab") { event.preventDefault() console.log('preventDefault', event) } } } } })IME ONの状態での入力中に、入力をJavaScriptで制御するのは難しいようで、半角英数ならば意図した通に動くものの、日本語入力中には意図した通り動作させることができませんでした。
試してみたい方は、以下codepen上でお試しください。See the Pen Try to apply max length restriction on input Japanese language (don't work well) by Yasunori MATSUOKA (@YasunoriMATSUOKA) on CodePen.
まとめ
半角英数で完結する言語うらやましい...日本語入力等のIME ONにする必要がある言語はこういうとき大変...
- 投稿日:2020-02-24T22:28:55+09:00
同期的に複数のファイルをform-dataを使って送るには
こんにちは、wattak777です。
一ファイルを送信する、というサンプルは幾つかあるのですが、requestとform-dataを組み合わせて送る場合、複数ファイルの場合、例えばforループで送るようにしても1つ目、2つ目、3つ目と送りきる前にどんどん送ってしまうため同期的に送ることが出来ないため、ちょっとサンプルを作ってみました。
サーバー側はmulterを使った以下のサンプル。
server.jsvar express = require( 'express' ) ; var app = express() ; var multer = require( 'multer' ) ; app.post('/file_upload', multer({dest: ファイルを置くパス}).single('my_file'), function (req, res) { console.log(req.file.path, req.file.originalname) ; res.sendStatus(200) ; }); var server = app.listen(12345, function() { console.log("listening at port %s", server.address().port) ; });で、本題のクライアント側は以下の実装。
request-promiseを使って同期的に送るようにしました。client.jsconst fs = require('fs') ; const request = require('request-promise') ; const FileNameList = [ 'test1.bin', 'test2.bin', 'test3.bin' ] ; var FileNameIndex = 0 ; var returnCode = httpPost() ; function httpPost() { const FormData = { my_file: { value: fs.createReadStream(ファイルのパス + FileNameList[FileNameIndex]), options: { filename: FileNameList[FileNameIndex], contentType: 'application/octet-stream' } } } const options = { uri: "http://サーバーのIPアドレス:12345/file_upload", formData: FormData, method: 'post', headers: { 'Content-Type': 'multipart/form-data' } } var response = request(options) .then( function(body) { console.log( 'then :' + body ) ; onEnd() ; }) .catch( function(err) { console.log( 'catch error :' + err ) ; }) ; return response.statusCode ; } function onEnd() { console.log( 'Index ' + FileNameIndex + ' is finish.' ) ; FileNameIndex = FileNameIndex + 1 ; if ( FileNameIndex >= FileNameList.length ) { console.log( 'End Operation.' ) ; } else { var res = httpPost() ; } } console.log( 'Start Operation.' ) ;とやると、クライアント側の表示は以下のようになります。
$ node form2_promise.js Start Operation. then :OK Index 0 is finish. then :OK Index 1 is finish. then :OK Index 2 is finish. End Operation.
- 投稿日:2020-02-24T22:16:44+09:00
face-api.jsでニコラ○・ケ○ジになる
顔認識がjsだけでもそれなりに動くのを今更知ったので触ってみた。
顔晒すのに抵抗感がある古い人間なので文中画像少なめです。成果物
Git: https://github.com/engabesi/face-nicolas
GithubPages: https://engabesi.github.io/face-nicolas/
やること
- expressでlocalhostを建てる
- face-api.jsで顔の矩形を取る
- 顔に画像を被せる
- GithubPagesにdeploy
face-api.js
今回顔認識に使うライブラリはこちら
https://github.com/justadudewhohacks/face-api.js
jsだけでそれなりの精度とパフォーマンスを出せます。
複数人も認識可能。
ランドマークや表情、顔認証も出来ます。
demoはこちら
https://justadudewhohacks.github.io/face-api.js/face_and_landmark_detection/注意点
face-api.js
はWebGLを使用しています。
その為ブラウザの設定でハードウェアアクセラレーションをオフにしている場合上手く動作しない可能性があります。expressでlocalサーバーを建てる
android等でも気軽にテストするためにまずはlocalhostで動作するようにします。
後にgithubPagesも利用したいため、以下の構成にします。構成root/ ├─ app.js └─ docs/ ├─ index.html ├─ images/ └─ js/ ├─ index.js └─ lib/ ├─ face-api.min.js └─ models/ ├─ tiny_face_detector_model-shard1 └─ tiny_face_detector_model-weights_manifest.json
face-api.min.js
, 及びmodels内ファイルは以下のrepoから取ってきます
https://github.com/justadudewhohacks/face-api.js/tree/master/dist
https://github.com/justadudewhohacks/face-api.js/tree/master/weightsexpressを導入します。
shellyarn init -y yarn add express
docs\index.html<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> <script defer src="./js/lib/face-api.min.js"></script> <style> body { margin: 0; padding: 0; width: 100vw; height: 100vh; display: flex; justify-content: center; align-items: center; } canvas { position: absolute; } </style> </head> <body> <video id="video" width="720" height="560" autoplay muted></video> <script defer src="js/index.js"></script> </body> </html>app.jsconst express = require("express"); const app = express(); const path = require("path"); app.listen(8080, () => { console.log("Running at Port 8080..."); }); app.use(express.static(path.join(__dirname, "docs"))); app.use((req, res) => res.sendStatus(404));これでshellに
node app.js
と打ち、http://localhost:8080/ にアクセスするとページが開きます(今は真っ白)webcamera設定
docs\index.jsconst startVideo = async video => { try { const constraints = { audio: false, video: {} }; const stream = await navigator.mediaDevices.getUserMedia(constraints); video.srcObject = stream; } catch (error) { console.error(error); } }; (async () => { const video = document.querySelector("video"); await startVideo(video); })();これでlocalhostを開くとwebcameraの映像が取れているはずです。
顔の矩形を取る
まずmodelを読み込みます。
docs\index.js+ const loadModels = async () => { + await Promise.all([ + faceapi.nets.tinyFaceDetector.loadFromUri(`/js/lib/models`) + ]); + }; (async () => { const video = document.querySelector("video"); + await loadModels(); await startVideo(video); })();次に顔を認識して矩形描画処理を追加します。
docs\index.js(async () => { const video = document.querySelector("video"); await loadModels(); await startVideo(video); // --- ADD --- video.addEventListener("play", () => { // overlay canvas作成 const canvas = faceapi.createCanvasFromMedia(video); document.body.append(canvas); const displaySize = { width: video.width, height: video.height }; faceapi.matchDimensions(canvas, displaySize); const tinyFaceDetectorOption = { // default 416 inputSize: 224, // default 0.5 scoreThreshold: 0.5 }; setInterval(async () => { const results = await faceapi.detectAllFaces( video, new faceapi.TinyFaceDetectorOptions(tinyFaceDetectorOption) ); if (results.length <= 0) return; // 検出結果をcanvasのサイズにリサイズ const resizedResults = faceapi.resizeResults(results, displaySize); // canvasの内容をクリア canvas.getContext("2d").clearRect(0, 0, canvas.width, canvas.height); // 矩形描画 faceapi.draw.drawDetections(canvas, resizedResults); }, 100); }); // --- ADD --- })();これでscoreが記載された矩形が顔に被さって表示されるようになります。
顔に画像を被せる
検出結果から座標を取ってその位置に画像を被せてみます。
docs\index.js(async () => { const video = document.querySelector("video"); + // 画像セットアップ + const image = new Image(); + image.src = `/images/cage_neutral.png`; await loadModels(); await startVideo(video); video.addEventListener("play", () => { const canvas = faceapi.createCanvasFromMedia(video); document.body.append(canvas); const displaySize = { width: video.width, height: video.height }; faceapi.matchDimensions(canvas, displaySize); const tinyFaceDetectorOption = { // default 416 inputSize: 224, // default 0.5 scoreThreshold: 0.5 }; setInterval(async () => { const results = await faceapi.detectAllFaces( video, new faceapi.TinyFaceDetectorOptions(tinyFaceDetectorOption) ); if (results.length <= 0) return; const resizedResults = faceapi.resizeResults(results, displaySize); canvas.getContext("2d").clearRect(0, 0, canvas.width, canvas.height); - // faceapi.draw.drawDetections(canvas, resizedResults); + resizedResults.forEach(detection => { + // 矩形のtopはデコあたりなので調整 + const marginVal = 0.4; + // 矩形の情報はdetection.boxに格納されている + const width = detection.box.width; + const height = detection.box.height * (1.0 + marginVal); + const x = detection.box.x; + const y = detection.box.y - detection.box.height * marginVal; + canvas.getContext("2d").drawImage(image, x, y, width, height); + }); }, 100); }); })();これで自分の顔に画像を貼り付けることが出来ました。
他にもmodelやdetection時に.with~~
とするだけで表情やランドマーク等の情報を取れます。
詳しくは公式README参照。自分のgitにあげている物にも画像調達に飽きて中途半端になっていますが表情を取るコードも記載してあります。GithubPages
コードをrepositoryに上げたら
repository > settings > Options > GithubPages > Sourceを
master branch /docs folder
にすればGithubPagesにデプロイすることが出来ます。
だから、ディレクトリ名をdocsにする必要があったんですね。注意点
上記コードでは画像のpathを直打ちしています。
github.io
にデプロイする場合は問題ありませんが、github.io/SUB_REPO/
にデプロイした場合、ルートURLがgithub.io
扱いとなり、SUB_REPOが飛ばされてしまってpathがおかしくなってしまいます。
jekyll
の使用をgithubは推奨していますが試して遊ぶだけの場合ちょっと面倒です。
暫定対策としてpath前に直接SUB_REPO名をくっつけてあげると一応動きます。まとめ
たった数分でブラウザ完結の顔認識ができるなんて素晴らしい世の中になりました。
現状のコードだとスマートフォンで見た場合矩形がものすごく横長になってしまっているのでその辺の対応をしだすとヘビーになるかもしれませんがちょっと遊んで見るぐらいだと非常に有用だと思います。他にも
clmtrackr.js
やpico.js
、更にはChromeの機能だけでできるShape Detection API
等様々なライブラリがありますので是非触って遊んでみてください。
- 投稿日:2020-02-24T21:37:55+09:00
kintoneで宣言的にフィールド値の検証を行う
先日、 Tynder という、TypeScript/JavaScript用のスキーマ検証ライブラリを公開しました。
Tynder は、TypeScriptのサブセット+独自の拡張文法から成るDSLによって
- 型の検査
- 単独の項目の必須・値の長さ・範囲や文字列パターンの検証
- 複数項目の相関や整合性検証の一部 (Union typeによる)
を宣言的に行うことができます。
また、カスタムエラーメッセージを表示することができます。今回はTynderを使用して、kintoneアプリのフィールド値を宣言的に検証したいと思います。
動機
kintoneは、標準で項目の必須チェック、文字列項目の文字数レンジチェック、数値項目のレンジチェック機能等を備えていますが、やや複雑な条件でのチェックを行うためには、JavaScriptによるカスタマイズを行う必要があります(例えば、条件付き必須、文字列のパターンマッチ)。
しかし、イベントハンドラ(または、分離した関数)で手続き的にチェックを記述するのは、可読性が低く、また、チェック対象のデータはフィールド型等のメタデータも含んでおり、データの階層が深く、正直触りたくありません。完成イメージ
サブテーブルを含む各フィールドに検証エラーのエラーメッセージを表示します。
以下のサンプルでは、敢えて標準の必須項目等は設定していませんが、必須などのエラーが表示されました。設定方法
アプリの設定画面から、以下のJavaScriptファイルを追加してください。
- tynder.min.js
- lib.js (下述)
- app.js (下述)
コード
lib.js// `event.record`をメタデータ(フィールド型等)を含まない普通のデータに変換します function mapRecord(rec) { const ret = {}; const keys = Object.keys(rec); const subTableMapper = x => mapRecord(x.value); for (const k of keys) { switch (rec[k].type) { case 'NUMBER': ret[k] = typeof rec[k].value === 'string' ? Number(rec[k].value.replace(/[,]/g, '')) : rec[k].value; break; case 'SUBTABLE': ret[k] = rec[k].value.map(subTableMapper); break; default: ret[k] = rec[k].value; break; } } return ret; } // サブテーブルのブランク行を削除します(新規行のみ) function removeBlankTableRow(rec, tableFieldCode) { const validRecs = []; for (const r of rec[tableFieldCode].value) { if (r.id !== null) { validRecs.push(r); continue; } const keys = Object.keys(r.value); for (const k of keys) { const q = r.value[k]; if (q.value !== void 0 && q.value !== null && q.value !== '') { validRecs.push(r); break; } } } rec[tableFieldCode].value = validRecs; return rec; } // フィールドにエラーメッセージを表示します function displayValidationErrorMessages(event, ctx) { for (const m of ctx.errors) { const dp = m.dataPath.split('.').map(x => x.split(':').slice(-1)[0]); const fieldCode = dp[0]; if (m.dataPath.includes('repeated).')) { const index = /\.\(([0-9]+):repeated\)\./.exec(m.dataPath); if (index) { const subFieldCode = dp[dp.length - 1]; event.record[fieldCode].value[Number(index[1])].value[subFieldCode].error = m.message; } } else { if (event.record[fieldCode]) { event.record[fieldCode].error = m.message; } } } event.error = 'Validation error'; return event; }app.js// アプリのレコード型を定義します // interfaceの各フィールドは、kintoneアプリのフィールドコードと一致させてください const definition = ` /** サブテーブル */ interface Table { itemName: string; itemValue: number; } /** 全ステータス共通のフィールド */ interface AppBase { } /** アプリ (ステータス1) */ interface App1 extends AppBase { @minLength(2) name: string; amount: number; table: Table[]; } // レコードのステータス毎に定義を持てば、殆どの条件付きチェックは // 条件分岐を含まない形の分解できる `; // イベントハンドラ kintone.events.on('app.record.edit.submit', function(event) { event.record = removeBlankTableRow(event.record, 'table'); // スキーマ検証を行います const schema = tynder.compile(definition); const ctx = {checkAll: true, schema}; const validated = tynder.validate(mapRecord(event.record), tynder.getType(schema, 'App1'), ctx); if (! validated) { const errText = JSON.stringify(ctx.errors, null, 2); console.error(errText); // エラーを表示します displayValidationErrorMessages(event, ctx); } return event; });さいごに
書かなくて良いコードは書かずに、楽しくプログラミングしたいですね?
- 投稿日:2020-02-24T20:57:38+09:00
MapBox GL JS でラベル付きマーカー
MapBox GL のサンプルにカスタムマーカーがあるので、それを参考に「ラベル付きのマーカー」を実現してみた。
動作するサンプルはこちら。
ソースコード
index.html
<!DOCTYPE html> <html> <head> <meta charset='utf-8' /> <title></title> <meta name='viewport' content='initial-scale=1,maximum-scale=1,user-scalable=no' /> <link href="https://fonts.googleapis.com/css?family=Open+Sans" rel="stylesheet"> <script src='https://api.tiles.mapbox.com/mapbox-gl-js/v1.8.0/mapbox-gl.js'></script> <link href='https://api.tiles.mapbox.com/mapbox-gl-js/v1.8.0/mapbox-gl.css' rel='stylesheet' /> <link rel="stylesheet" type="text/css" href="./index.css" media="screen"> <style> </style> </head> <body> <div id='map'></div> <template id="marker"> <div class="marker-container"> <span id="title" class="marker-title"></span> <img id="marker-icon" src="https://img.icons8.com/ios-filled/40/0000FF/marker.png"> </div>> </template> <script src="./index.js"></script> </body> </html>index.css
body { margin: 0; padding: 0; } #map { position: absolute; top: 0; bottom: 0; width: 100%; } .marker-container { display: flex; flex-direction: column; align-items: center; cursor: pointer; } .marker-title { max-width: 100px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .marker-icon { height: 30px; width: auto; background-size: cover; } .mapboxgl-popup { max-width: 200px; } .mapboxgl-popup-content { text-align: center; font-family: 'Open Sans', sans-serif; }index.js
const geojson = { type: 'FeatureCollection', features: [{ type: 'Feature', geometry: { type: 'Point', coordinates: [-77.032, 38.913] }, properties: { title: 'Mapbox', description: 'Washington, D.C.' } }, { type: 'Feature', geometry: { type: 'Point', coordinates: [-122.414, 37.776] }, properties: { title: 'Mapbox', description: 'San Francisco, California' } }] }; const map = new mapboxgl.Map({ container: 'map', center: [-96, 37.8], zoom: 3, style: { "version": 8, "sources": { "OSM": { "type": "raster", "tiles": ['http://a.tile.openstreetmap.org/{z}/{x}/{y}.png'], "tileSize": 256 } }, "layers": [{ "id": "OSM", "type": "raster", "source": "OSM" }] } }); geojson.features.forEach(marker => { // Create element for marker from template const template = document.getElementById('marker'); const clone = document.importNode(template.content, true); const el = clone.firstElementChild; clone.getElementById('title').innerHTML = marker.properties.description; new mapboxgl.Marker(el, { anchor: 'bottom' }) .setLngLat(marker.geometry.coordinates) .setPopup(new mapboxgl.Popup({ offset: 60, anchor: 'bottom' }) // add popups .setHTML('<h3>' + marker.properties.title + '</h3><p>' + marker.properties.description + '</p>')) .addTo(map); });
- マーカーとして使用する HTML 要素を
template
タグで用意しておきます。- マーカーごとに
template
から要素を生成(importNode
で複製しないと同じ要素が使い回されるので注意)してラベルを設定、その要素から MapBox の Marker を生成します。※地図を OpenStreetMaps にしているのは、MapBox の地図を表示するには AccessToken を設定する必要があるためです。
参考
- 投稿日:2020-02-24T20:55:39+09:00
Testable Redux ~ React と Redux のテスト戦略 ~
本記事では Redux を使用した場合の React コンポーネントに対するテスト方法を考察します。
Redux に接続された React コンポーネントは、コンポーネント内のプロパティを Redux Store の state と同期させています。 Redux に強く依存しているコンポーネントをどのように Testable にしていけば良いのでしょうか。記事のタイトルは、t-wada 先生の言葉をお借りして、Testable Redux とつけさせていただきました。
純粋な React コンポーネントをテストする
さて、まずはじめに純粋な React のコンポーネントに対するテスト方法を振り返ってみます。Facebook に習えば、jest の公式ドキュメントでも紹介されている
jest
とenzyme
を組み合わせた方法が一般的でしょう。以下のようなシンプルなコンポーネントを例として使用します。
count
を保持する state をもち、onClick
で state を変更します。簡単のために Functional Component とし、State Hooks を使用します。後のテストのために各タグにはカスタムデータ属性(data-test)を付与しておきます。プロダクションビルドでカスタムデータ属性を取り除く方法もあります。こちらの記事にて詳細に解説されていました。import React, { useState } from "react"; export default () => { const [count, setCount] = useState(0); return ( <div> <h3 data-test="count">count: {count}</h3> <button data-test="count-up" onClick={() => setCount(count + 1)}>⬆︎</button> <button data-test="count-down" onClick={() => setCount(count - 1)}>⬇︎</button> </div> ); };これに対するテストは以下のように記述できます。初期値が 0 であること、ボタンをクリックして 1 に変わることを確認します。
import React from "react"; import { shallow } from "enzyme"; import Counter from "./PureReactCounter"; import testConfigure from "../../testConfigure"; testConfigure(); const sel = (id: string) => `[data-test="${id}"]`; describe("<Counter /> コンポーネント", () => { const Component = shallow(<Counter />); it("ボタンをクリックしてカウントアップする", () => { expect(Component.find("h3").text()).toEqual("count: 0"); Component.find(sel("count-up")).simulate("click"); expect(Component.find("h3").text()).toEqual("count: 1"); }); });なお、
testConfigure
には以下を記述しておきます。enzyme を使用する場合のお約束のようなものです。enzyme の installationにて解説されています。import Enzyme from "enzyme"; import EnzymeAdapter from "enzyme-adapter-react-16"; export default () => Enzyme.configure({ adapter: new EnzymeAdapter() });結果は 8ms でした。このテストでは
shallow()
を使用しています。Shallow レンダリングは、コンポーネントをユニットテストの範囲でテストできるように制限します。テストが子コンポーネントの動作を間接的にアサートしないようにしてくれます。コンポーネントは子コンポーネントに依存させず、常にステートレスに保つことによって Shallow レンダリングによるテストができ、高速です。PASS src/components/PureReactCounter.test.tsx <Counter /> コンポーネント ✓ ボタンをクリックしてカウントアップする (8ms)ちなみに、
mount()
は完全な DOM レンダリングを行います。DOM API とやり取りする可能性のあるコンポーネントである場合や、より高次のコンポーネントにラップされているコンポーネントをテストする必要がある場合に最適です。ただしその一方で多くの依存関係を考慮することによりテストに要する時間は増える傾向にあります。PASS src/components/PureReactCounter.test.tsx <Counter /> コンポーネント Shallowレンダリング ✓ ボタンをクリックしてカウントアップする (8ms) Fullレンダリング ✓ ボタンをクリックしてカウントアップする (8ms)結果として実行時間はほとんど変わりませんでした。これは今回実装したコンポーネントが他のコンポーネントや API などと依存していないシンプルなコンポーネントであるためです。
Redux の使用を開始する
さて、先ほど実装したコンポーネントは、
count
を保持する state をもち、onClick
で state を変更していました。このcount
という state を Redux の store で管理します。実装例を紹介する前に Redux のライフサイクルのおさらいをしましょう。
- View: ユーザが操作を行い、handleClick() などのイベント起動 function が実行される。
- ActionCreater: イベント起動 function は ActionCreater を通して action を生成する。
- Dispacther: action は Dispater に渡され、Reducer に流れる。
- Reducer: Reducer は action の type に応じて新しい state を返却する。
説明の粒度は荒いですが、おおまかにこのような流れで Redux による状態の管理が行われます。
さて、ディレクトリ構成は以下のようにして実装を進めていきます。
src/ ├── index.tsx ├── App.tsx ├── components │ ├── Counter.tsx │ └── Counter.test.tsx └── store ├── actions │ └── counterActions.ts ├── reducers │ └── counterReducer.ts └── store.tsAction (actions/counterActions.ts)
Action の定義を行います。今回はカウントアップとカウントダウンする2種類の action があるので事前に定義しておき、他のファイルから参照できるようにしておきましょう。
export const INCREMENT = "INCREMENT"; export const DECREMENT = "DECREMENT"; export interface ICountReducerAction { type: typeof INCREMENT | typeof DECREMENT; }Reducer (reducers/counterReducer.ts)
Reducer は飛んできた action の type に応じて state 返却する処理を書くのでした。
INCREMENT
の場合は+1
、DECREMENT
の場合は-1
された新しい state を返却しています。import { ICountReducerAction, INCREMENT, DECREMENT } from "../actions/counterActions"; const initialState = 0; const counterReducer = (state = initialState, action: ICountReducerAction) => { switch (action.type) { case INCREMENT: return state + 1; case DECREMENT: return state - 1; default: return state; } }; export default counterReducer;Store
先に定義した
reducer
を束ねて、store
を作成しましょう。今回reducer
は1つですが、複数使用することになることも考慮してcombineReducers()
を使用しています。import { createStore } from "redux"; import { combineReducers } from "redux"; import counterReducer from "./reducers/counterReducer"; const reducer = combineReducers({ count: counterReducer }); export default createStore(reducer);Component から Redux の store を参照する
Redux の state を View に表示できるようにコンポーネントを実装していきましょう。まず初めに純粋に Redux の store を直接参照するような実装を考えます。
ReactDOM.render()
をstore
からsubscribe()
します。import React from "react"; import ReactDOM from "react-dom"; import App from "./App"; import store from "./store/store"; const render = () => ReactDOM.render(<App />, document.getElementById("root")); render(); store.subscribe(render);コンポーネントは以下のように実装します。この場合はどうでしょう、テストは容易でしょうか。
import React from "react"; import store from "../../store/store"; import { INCREMENT, DECREMENT } from "../../store/actions/counterActions"; export default () => { const count = store.getState().count; // store から直接参照する return ( <div> <h3 data-test="count">count: {count}</h3> <button onClick={() => store.dispatch({ type: INCREMENT })}>⬆︎</button> <button onClick={() => store.dispatch({ type: DECREMENT })}>⬇︎</button> </div> ); };テストコードを振り返ってみましょう。このままでは Redux の store を参照ができないため、ボタンをクリックした後に発火するはずの Reducer が起動しません。結果として count は 0 のままです。
const sel = (id: string) => `[data-test="${id}"]`; describe("<Counter /> コンポーネント", () => { describe("Shallowレンダリング", () => { const Component = shallow(<Counter />); it("ボタンをクリックしてカウントアップする", () => { expect(Component.find("h3").text()).toEqual("count: 0"); Component.find(sel("count-up")).simulate("click"); // Redux store の state が変更されない expect(Component.find("h3").text()).toEqual("count: 1"); // 0 のまま }); }); describe("Fullレンダリング", () => { const Component = mount(<Counter />); it("ボタンをクリックしてカウントアップする", () => { expect(Component.find("h3").text()).toEqual("count: 0"); Component.find(sel("count-up")).simulate("click"); expect(Component.find("h3").text()).toEqual("count: 1"); }); }); });
テスト結果(クリックして開く)
FAIL src/components/DirectAccessReduxStore.test.tsx <Counter /> コンポーネント Shallowレンダリング ✕ ボタンをクリックしてカウントアップする (11ms) Fullレンダリング ✕ ボタンをクリックしてカウントアップする (6ms) ● <Counter /> コンポーネント › Shallowレンダリング › ボタンをクリックしてカウントアップする expect(received).toEqual(expected) // deep equality Expected: "count: 1" Received: "count: 0" 13 | expect(Component.find("h3").text()).toEqual("count: 0"); 14 | Component.find(sel("count-up")).simulate("click"); > 15 | expect(Component.find("h3").text()).toEqual("count: 1"); | ^ 16 | }); 17 | }); 18 | describe("Fullレンダリング", () => { at Object.<anonymous> (src/components/DirectAccessReduxStore.test.tsx:15:43) ● <Counter /> コンポーネント › Fullレンダリング › ボタンをクリックしてカウントアップする expect(received).toEqual(expected) // deep equality Expected: "count: 1" Received: "count: 0" 21 | expect(Component.find("h3").text()).toEqual("count: 0"); 22 | Component.find(sel("count-up")).simulate("click"); > 23 | expect(Component.find("h3").text()).toEqual("count: 1"); | ^ 24 | }); 25 | }); 26 | }); at Object.<anonymous> (src/components/DirectAccessReduxStore.test.tsx:23:43)Redux と React のコンポーネントが強く依存する関係になっており、React のコンポーネント単体としてテストしづらくなっています。この構成を変えていきましょう。まずは React と Redux の間の依存関係を切り離す方法を考えます。
react-redux
のconnect()
を使用して React と Redux の依存を引き剥がず最も代表的な実装パターンとして react-redux の
connect()
を使用する方法が挙げられます。これは、コンポーネントをマウントする時点で子コンポーネントや依存関係にある構成要素を考える必要がなく、シンプルです。Counter コンポーネントと Redux store が強い依存関係にある実装から、なるべく疎結合になるように配慮します。いわゆる DI (Dependency Injection) の考え方にしたがって、Counter コンポーネントの store を外から props として注入できるように変更します。ただ単に props によって注入するだけではなく、Redux store との接合は
connect()
によって行います。詳細な手続きは公式チュートリアルを参照しましょう。このようなコンポーネントを HOC (Higher Order Component) と呼びます。詳細はこちらの記事が参考になります。import React from "react"; import { connect } from "react-redux"; import { Dispatch } from "redux"; import { INCREMENT, DECREMENT } from "../../store/actions/counterActions"; interface ICounterState { counter: number; } interface ICounterProps { count: number; increment: any; decrement: any; } // props として store を流し込んで DI できるるようにする。 export const Counter = (store: ICounterProps) => { const { count, increment, decrement } = store; return ( <div> <div data-testid="count">count: {count}</div> <button onClick={increment}>⬆︎</button> <button onClick={decrement}>⬇︎</button> </div> ); }; const mapStateToProps = (state: ICounterState) => ({ count: state.counter }); const mapDespatchToProps = (dispatch: Dispatch) => ({ increment: () => dispatch({ type: INCREMENT }), decrement: () => dispatch({ type: DECREMENT }) }); // 第一引数の mapStateToProps は component に渡す props を制御する // 第二引数の mapDespatchToProps は reducer を呼び出して、redux で管理している state を更新する // Counter は取得したデータを props として扱いたい component を指定する export default connect(mapStateToProps, mapDespatchToProps)(Counter);さて、ここまでできれば勝てる気がしてきました。テストを書きます。
import React from "react"; import { mount, shallow } from "enzyme"; import { Provider } from "react-redux"; import { createStore } from "redux"; import sinon from "sinon"; import ConnectedCounter, { Counter } from "./ReactRedux"; import { reducer } from "../../store/store"; import testConfigure from "../../testConfigure"; testConfigure(); const sel = (id: string) => `[data-test="${id}"]`; describe("<Counter /> コンポーネント", () => { describe("Shallowレンダリング", () => { const props = { count: 0, increment: sinon.spy(), // カウントアップするボタンを押した挙動を確認するためにスパイを差し込む decrement: sinon.spy() }; const shallowComponent = shallow(<Counter {...props} />); it("ボタンをクリックしてカウントアップする", () => { expect(shallowComponent.find("h3").text()).toEqual("count: 0"); shallowComponent.find(sel("count-up")).simulate("click"); expect(props.increment).toHaveProperty("callCount", 1); }); }); describe("Fullレンダリング", () => { const getWrapper = (mockStore = createStore(reducer, { count: 0 })) => mount( <Provider store={mockStore}> <ConnectedCounter /> </Provider> ); it("ボタンをクリックしてカウントアップする", () => { const wrapper = getWrapper(); expect(wrapper.find("h3").text()).toEqual("count: 0"); wrapper.find(sel("count-up")).simulate("click"); expect(wrapper.find("h3").text()).toEqual("count: 1"); }); }); });上記のテストでは、Shallow レンダリングと Full レンダリングの2つの方法でテストを記述しました。
Shallow レンダリングでは Redux の store を含めず、コンポーネント単体でテストを行います。そのため、カウントアップするボタンを押した際のハンドラ関数は
simon.spy()
を使用してスパイを差し込んで挙動を確認します。一方で、Full レンダリングの場合は Redux の store だけをモックとして作成し、
<Provider>
の store に DI します。こちらの手法の方が Redux の store を含めたテストにはなるのですが、実行時間が増える傾向にあるため注意が必要です。ちなみに実行時間を比較すると以下のようになります。PASS src/components/container/ReactRedux.test.tsx <Counter /> コンポーネント Shallowレンダリング ✓ ボタンをクリックしてカウントアップする (9ms) Fullレンダリング ✓ ボタンをクリックしてカウントアップする (44ms)mount するのはインテグレーションテストだユニットテストではない、shallow の方が高速だ、などの論争があります。個人的な意見ですが、チーム開発を行う上ではシンプルかつ可読性を維持したテストコードを書いた方がむしろチームとしてアジリティが上がるのではないかと考えています。学習コストも高いですしね。
それでもプロダクトコードの量が大きくなるにつれて、テストの実行時間の増加によりアジリティが下がるケースもあります。shallow レンダリングと full レンダリングの実行時間の差は ms 程度ですが、塵も積もれば山となるということです。テストの方針を決めるのは難しいですね。Redux Hooks を使用してコード量を減らす
怠惰で傲慢な我々は、現状に満足することはありません。上に挙げた実装は React と Redux の依存関係を引き剥がすことで、Shallow レンダリングによるテストが実現できるようになりました。その一方で少し複雑な実装をしなければならないように感じます。この章では Redux Hooks を使用して簡単に記述できる方法をご紹介します。
React の Hooks API は、Functional コンポーネントに対してローカルコンポーネントの state を使用できるようになる優秀な機能です。
react-redux
でも、既存のconnect()
を使用して実装された HOC (Higher Order Component) の代わりとして Hooks API を提供するようになりました。これを使用すると、前章の実装のようにコンポーネントをにラップして HOC を作るような面倒な作業は必要ありません。Redux store に subscribe して action を dispatch できます。導入は非常に簡単です。
useSelector()
とuseDispatch()
を使用しましょう。import React from "react"; import { useSelector, useDispatch } from "react-redux"; import { INCREMENT, DECREMENT } from "../../store/actions/counterActions"; export default () => { const count = useSelector((state: any) => state.count); const dispatch = useDispatch(); const increment = () => { dispatch({ type: INCREMENT })}; const decrement = () => { dispatch({ type: DECREMENT })}; return ( <div> <h3>count: {count}</h3> <button data-test="count-up" onClick={increment}>⬆︎</button> <button data-test="count-down" onClick={decrement}>⬇︎</button> </div> ); };コードがとても短くなりました。かなりスマートです。あれ? でもおかしいですね、少し振り返ってみましょう。
connect()
を使用して HOC を作ったときには純粋な React コンポーネントと Redux との依存関係を引き剥がすことに成功していました。今回、Redux Hooks を使用した実装では、また依存関係が強くなってしまいました。これでは Full レンダリングを行うようなテストしか記述できません。describe("<Counter /> コンポーネント", () => { describe("Fullレンダリング", () => { const getWrapper = (mockStore = createStore(reducer, { count: 0 })) => mount( <Provider store={mockStore}> <ConnectedCounter /> </Provider> ); it("ボタンをクリックしてカウントアップする", () => { const wrapper = getWrapper(); expect(wrapper.find("h3").text()).toEqual("count: 0"); wrapper.find(sel("count-up")).simulate("click"); expect(wrapper.find("h3").text()).toEqual("count: 1"); }); }); });Redux Hooks でも Redux と React を分離する
基本的な考え方は今までと同様です。Redux と接続する部分を DI できるようにすれば OK です。
useSelector()
とuseDispatch()
を使用している箇所を外に出してやりましょう。import { useSelector, useDispatch } from "react-redux"; import { IRootState } from "../../store/store"; import { INCREMENT, DECREMENT } from "../../store/actions/counterActions"; import { Dispatch } from "redux"; interface ICounterProps { count: number; increment: Dispatch; decrement: Dispatch; } export const Counter = (props: ICounterProps) => { const { count, increment, decrement } = props; return ( <div> <h3>count: {count}</h3> <button data-test="count-up" onClick={increment}>⬆︎</button> <button data-test="count-down" onClick={decrement}>⬇︎</button> </div> ); }; export default (props: any) => { const count = useSelector<IRootState>(state => state.count); const dispatch = useDispatch(); const _props = { count, increment: () => dispatch({ type: INCREMENT }), decrement: () => dispatch({ type: DECREMENT }), ...props }; return <Counter {..._props} />; };当然といえば当然ですが、テストコードは
react-redux
のconnect()
を使用した場合の実装と同じになります。
テストコード(クリックして開く)
const sel = (id: string) => `[data-test="${id}"]`; describe("<Counter /> コンポーネント", () => { describe("Shallowレンダリング", () => { const props = { count: 0, increment: sinon.spy(), decrement: sinon.spy() }; const shallowComponent = shallow(<Counter {...props} />); it("ボタンをクリックしてカウントアップする", () => { expect(shallowComponent.find("h3").text()).toEqual("count: 0"); shallowComponent.find(sel("count-up")).simulate("click"); expect(props.increment).toHaveProperty("callCount", 1); }); }); describe("Fullレンダリング", () => { const getWrapper = (mockStore = createStore(reducer, { count: 0 })) => mount( <Provider store={mockStore}> <ConnectedCounter /> </Provider> ); it("ボタンをクリックしてカウントアップする", () => { const wrapper = getWrapper(); expect(wrapper.find("h3").text()).toEqual("count: 0"); wrapper.find(sel("count-up")).simulate("click"); expect(wrapper.find("h3").text()).toEqual("count: 1"); }); }); });さいごに
結局のところ、どのアプローチを採用した場合も実際の Redux store を使用した React コンポーネントのテストは高速です。Redux の設計は、Action、Reducer、State のそれぞれが互いに分離される方法のため、テストに非常に適しています。
このように Dependency Injection しやすい設計のもと Redux が作られているので、このようなシンプルな構造を取ることができました。今回はご紹介できませんでしたが、Redux Saga や Redux Thunk を使用した場合のアーキテクチャでも同様にテストはシンプルに記述できます。React をとりまくエコシステムは素晴らしいですね。
- 投稿日:2020-02-24T20:54:30+09:00
Rails+AxiosでCSRF対策用のトークンを使う設定
Rails+WebpackerのJavaScriptでAxiosを使うときは、POSTやPATCHで送信する際にCSRF対策用のトークンを指定する必要があります。送信のたびに指定するのは面倒なので、あらかじめ設定しておきましょう。
AxiosのInterceptorsの機能を使います。送信前にHTTPメソッドがPOST, PUT, PATCH, DELETEだったら、meta要素からトークンを取り出してHTTPヘッダにセットします。ここではjQueryを使っています。
サンプル: https://github.com/kazubon/blog-rails6-vuejs
app/javascript/axios_config.jsimport Axios from 'axios'; Axios.interceptors.request.use((config) => { if(['post', 'put', 'patch', 'delete'].includes(config.method)) { config.headers['X-CSRF-Token'] = $('meta[name="csrf-token"]').attr('content'); } return config; }, (error) => { return Promise.reject(error); });このJavaScritptをどこかで動かします。とりあえずapplication.jsに入れとけばいいでしょう。
app/javascript/packs/application.jsimport '../axios_config';あとは、ふつうにAxiosが使えます。
import Axios from 'axios'; Axios.post('/entries.json', { entry: this.entry });
- 投稿日:2020-02-24T20:07:18+09:00
住所からGPS座標を、逆にGPS座標から住所を求める JavaScript (AppleScript)
概要
住所から、そこのGPS座標(ジオタグ)を提供するWebサービスと、逆にGPS座標から住所名を提供するサービスがありますね。
Google Maps の API などがありますが、無償で利用できるものとして、次の2つを選択しました。住所やランドマーク名をGPS座標に変換するサービス
Geocoding.jp
住所だけでなく、ランドマーク名で、GPS座標が XML 形式で提供されます。GPS座標から住所を求める
農研機構の簡易逆ジオコーディングサービス
(従来の「https://www.finds.jp/」 は、2020年4月1日から完全に https://aginfo.cgk.affrc.go.jp/index.html.ja に移行するとのことですが、既にこのURLが使えるようになっています。)
GPS座標を渡すと、住所が JSON 形式で提供されます。ソース
このソースは OS X の「スクリプトエディタ.app」の JavaScript で書いています。
これをスクリプトエディタで新規に JavaScript としてコピペして、ユーザーディレクトリ、ライブラリー、script フォルダーに保存しておくと、メニューバーのスクリプトアイコンから直接起動できて便利です。住所やランドマークからGPS座標を求める JavaScript
adrs2gps_java.scpt// 住所等から緯度、軽度を求める // https://www.geocoding.jp/ のサービスを利用 // 戻り値は XML 形式 var app = Application.currentApplication() app.includeStandardAdditions = true var baseURL = 'https://www.geocoding.jp/api/?q=' var adr = app.displayDialog("住所またはランドマーク名を入力", {defaultAnswer: ""}).textReturned var ascAdr = encodeURIComponent(adr) var URL = baseURL + ascAdr var ans = app.doShellScript('curl -s ' + URL) if (ans.indexOf('error') > 0) { app.displayDialog('該当する住所が見つかりませんでした。') } else { var jyuusho = ans.split('<address>')[1].split('</address>')[0] var ido = ans.split('<lat>')[1].split('</lat>')[0] var keido = ans.split('<lng>')[1].split('</lng>')[0] app.displayDialog(jyuusho + 'の緯度,経度は\r' + ido + ', ' + keido + ' です。\r緯度,経度をクリップボードにコピーします。') app.setTheClipboardTo(ido + ',' + keido) }GPS座標から住所を求める JavaScript
gps2adrs_java.scpt// 緯度、軽度から住所を求める // https://www.finds.jp/ → https://aginfo.cgk.affrc.go.jp/ws/rgeocode.php のサービスを利用 // 2020/4/1 以降は「https://aginfo.cgk.affrc.go.jp/ws/rgeocode.php」に完全移行 // 戻り値は JSON 形式 var app = Application.currentApplication() app.includeStandardAdditions = true var baseURL = '"https://aginfo.cgk.affrc.go.jp/ws/rgeocode.php?json\&' var latlon = app.displayDialog("緯度,経度を入力", {defaultAnswer: ""}).textReturned var lat = latlon.split(',')[0] var lon = latlon.split(',')[1] var URL = baseURL + 'lat=' + lat + '\&lon=' + lon + '"' var ans = app.doShellScript('curl -s ' + URL) var jsonData = JSON.parse(ans) var adrs = '' switch (jsonData['status']) { case 400: app.displayDialog("データを取得できませんでした。"); break; case 200: case 202: var pname = jsonData['result']['prefecture']['pname'] if (pname !== undefined) adrs = adrs + pname var mname = jsonData['result']['municipality']['mname'] if (mname !== undefined) adrs = adrs + mname try {var sect = jsonData['result']['local'][0]['section'] } catch(e) {} if (sect !== undefined) adrs = adrs + sect try {var hnumb = jsonData['result']['local'][0]['homenumber'] } catch(e) {} if (hnumb !== undefined) adrs = adrs + hnumb try {var aza = jsonData['result']['aza'][0]['name'] } catch(e) {} if (aza !== undefined) adrs = adrs + aza app.displayDialog(lat + ', ' + lon + 'の住所\r' + adrs + '\nをクリップボードにコピーします。') app.setTheClipboardTo(adrs) }
- 投稿日:2020-02-24T20:04:50+09:00
HTML5 の pattern 属性で全角英数と半角カタカナを排除するバリデーションの正規表現サンプル
サンプルコード
サンプルコード前半の input 要素にて、全角英数と半角カタカナを排除するバリデーションを実施。
サンプルコード後半の script 要素にて、排除する範囲の文字を表示。<!DOCTYPE html> <html lang="ja"> <head> <meta charset="utf-8"> <title>The pattern attribute</title> </head> <body> <form method="get"> <input type="text" name="foo" size="32" title="全角英数と半角カタカナは入力不可" placeholder="アイウエオ12345" pattern="^[^\uFF10-\uFF19\uFF21-\uFF3A\uFF41-\uFF5A\uFF61-\uFF9F]*$" required > <input type="submit" value="送信"> </form> <script> document.write('全角数字: '); for(let c=0xFF10; c<=0xFF19; c++){ document.write(String.fromCodePoint(c)); } document.write('<br>'); document.write('全角英字(大文字): '); for(let c=0xFF21; c<=0xFF3A; c++){ document.write(String.fromCodePoint(c)); } document.write('<br>'); document.write('全角英字(小文字): '); for(let c=0xFF41; c<=0xFF5A; c++){ document.write(String.fromCodePoint(c)); } document.write('<br>'); document.write('半角カタカナ: '); for(let c=0xFF61; c<=0xFF9F; c++){ document.write(String.fromCodePoint(c)); } document.write('<br>'); </script> </body> </html>実行結果
実行環境: macOS Catalina + Google Chrome 79.0.3945.130
入力前の表示
未入力で送信ボタンを押した場合
排除する文字が含まれている状態で送信ボタンを押した場合
この例では title 属性で指定したテキストも表示されているが、このあたりの表示デザイン等はブラウザ依存になっている。「指定されている形式で入力してください。」が他のブラウザでは異なる文言になっていたり、「全角英数と半角カタカナは入力不可」というテキストが表示されなかったりするので注意。
排除する範囲の文字
今回のバリデーションで排除する文字の一覧 (サンプルが script 要素で出力している内容)。
全角数字: 0123456789 全角英字(大文字): ABCDEFGHIJKLMNOPQRSTUVWXYZ 全角英字(小文字): abcdefghijklmnopqrstuvwxyz 半角カタカナ: 。「」、・ヲァィゥェォャュョッーアイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワン゙゚Unicode コードポイントによる範囲。
- 全角数字: 0xFF10 〜 0xFF19
- 全角英字(大文字): 0xFF21 〜 0xFF3A
- 全角英字(小文字): 0xFF41 〜 0xFF5A
- 半角カタカナ: 0xFF61 〜 0xFF9F
参考資料
- 投稿日:2020-02-24T19:26:23+09:00
javascriptのことをバックエンドと思い込んだ男子大学生がGoogle App Scriptで優勝する記事です
初めに
自分の勤めている職場で、GASを使用した職場内の業務効率化をするプログラムの改善を自分がした際に行ったことをこの記事で挙げます。もしGasを使ってバックエンドっぽいことをしたい方は参考にしてみてください。(バックエンドといっても、SpreadsheetをDBみたいに使うだけです。
あれ、これってバックエンドて言えなくね?)本題
今まで使用していた表の機能
- スタッフごとに一か月間の日勤(昼間に働く)の時間、夜勤の時間、休憩時間の情報を取得し、表をGoogleSpreadsheetのシートへ作成・表示する。
- スタッフの勤務状況はGoogleカレンダーで管理されているので、スタッフの勤務状況はGoogleカレンダーIDから取得する。
- もし新規のスタッフの勤務状況を追加または既存のスタッフの勤務状況を削除して表を作成する際は、GASのコードへ直接追加するスタッフ名とそのカレンダーIDを追加するか、削除するスタッフ名とそのカレンダーIDを削除しなくてはいけない。⇒GASを扱える人がいないと追加と削除が難しい
改善する際の要件
- 表を作成する際に、もしスタッフを追加または削除する場合はGASのコードへじかに書き込まなくても良い状態にする。
ではどうするか
SpreadSheatにDBの役割を持ったシートを作る。そこへ表に必要なスタッフ名とそのカレンダーIDを登録することで、もし追加または削除が起きた際でも該当者のシートへの追加または削除をすることにより対応する。
実際のコード
ただ関数の中に処理を書き込んでもよかったのですが、それだと面白くないし処理の変更にも対応しにくいので、行う処理ごとにMVC的な役割を持たせ、役割ごとに関数内の処理を定めてみました。(実際のファイル名は
code.gas
ですが、そのままここでコードを見せると文字の色が変わらず見づらいので、ファイル名をcode.js
にしています。)Model
基本的にここではクライアント(表の作成者)からのデータの取得、DB周りの処理を行います。
ここで行うことは以下の7つです。
- DBの追加に関するクライアントからの入力値を取得する。
- DBの削除に関するクライアントからの入力値を取得する。
- クライアントからの入力を基にスタッフの追加を実行する。
- クライアントからの入力を基にスタッフの削除を実行する。
- 表の作成に関するクライアントからの入力値を取得する。
- 表を作成する際に必要なデータをDBから取得する。
- DBからスタッフ名とそのカレンダーのIDのデータを取得する。
GASでのコード
code.js//1. DBの追加に関するクライアントからの入力値を取得する。 function getStaffInfo() { const name = Browser.inputBox('登録者の名前を入力してください','お名前', Browser.Buttons.OK_CANCEL); switch(name) { case 'cancel': Browser.msgBox("処理を終了します。"); break; case "": Browser.msgBox("名前が空です。処理を終了します。",Browser.Buttons.OK); break; default: const getId = Browser.inputBox('登録者のカレンダーIDを入力してください。','ID', Browser.Buttons.OK_CANCEL); switch(getId) { case 'cancel': Browser.msgBox("処理を終了します。"); break; case "": Browser.msgBox("カレンダーIDが空です。処理を終了します。",Browser.Buttons.OK); break; default: return [name, getId]; } } } //2. DBの削除に関するクライアントからの入力値を取得する。 function getStaffName() { const name = Browser.inputBox('該当者の名前を入力してください','お名前', Browser.Buttons.OK_CANCEL); switch(name) { case 'cancel': Browser.msgBox("処理を終了します。"); break; case "": Browser.msgBox("名前が空です。処理を終了します。",Browser.Buttons.OK); break; default: return name; } } //3. クライアントからの入力を基にスタッフの追加を実行する。 function writeDB(sheet, array) { let n = 1; while(sheet.getRange(n, 1).getValue()) { n++; continue; } for(let i=0; i < array.length; i++) { sheet.getRange(n, i+1).setValue(array[i]); } } //4. クライアントからの入力を基にスタッフの削除を実行する。 function deleteDB(sheet, name) { let n = 1; while(sheet.getRange(n, 1).getValue() !== name) { if(sheet.getRange(n, 1).getValue() === "") { Browser.msgBox("該当者がいません。処理を終了します。",Browser.Buttons.OK); return; } n++; continue; } sheet.deleteRow(n); } //5. 表の作成に関するクライアントからの入力値を取得する。 function getTime() { const year = Browser.inputBox('YYYYの形式で入力してください。例 2018','取得年', Browser.Buttons.OK_CANCEL); if (year == 'cancel'){ Browser.msgBox("処理を終了します。",Browser.Buttons.OK); return; } if (year.match(/^20[0-9].$/) == null){ Browser.msgBox("入力形式が違います。最初からやり直してください。",Browser.Buttons.OK); return; } else { const month = Browser.inputBox('MMの形式で入力してください。例 04 ','取得月', Browser.Buttons.OK_CANCEL); if (month == 'cancel') { Browser.msgBox("処理を終了します。",Browser.Buttons.OK); return; } if (month.match(/^(01|02|03|04|05|06|07|08|09|10|11|12)$/) == null) { Browser.msgBox("入力形式が違います。最初からやり直してください。",Browser.Buttons.OK); return; } return [year, month]; } } //6. 表を作成する際に必要なデータをDBから取得する。 function getInfoFromDB(sheet) { let info = {}; let n = 1; while(sheet.getRange(n, 1).getValue() !== "") { const key = sheet.getRange(n, 1).getValue(); const value = sheet.getRange(n, 2).getValue(); info[key] = value; n++; continue; } return info; } //7. DBからスタッフ名とそのカレンダーのIDのデータを取得する。 function getResult(info, year, month) { let result = {} const startDay = new Date(year +'/'+ month + '/'+ 1); const lastDay = new Date(startDay.getFullYear(), startDay.getMonth() + 1,0, 0);//このままでは月の最終日が含まれない lastDay.setDate(lastDay.getDate()+1);//月の最終日を含める for(let key in info) { const calendar = CalendarApp.getCalendarById(info[key]); const events = calendar.getEvents(startDay, lastDay); let day = 0; let night = 0; let rest = 0; let money = 0; //スタッフごとの日勤の時間と夜勤の時間の合計を求める for(let i=0; i<events.length; i++) { const endTime = events[i].getEndTime().getHours(); const workEnd = (events[i].getEndTime().getHours()*60 + events[i].getEndTime().getMinutes())/60; const workStart = (events[i].getStartTime().getHours()*60 + events[i].getStartTime().getMinutes())/60; //夜勤の場合 if(endTime > 22.0) { day += workEnd - workStart - 1.0; night += workEnd - 22.0; } else { //日勤の場合 day += workEnd - workStart; } //休憩時間の合計を求める const str = events[i].getDescription();//イベントの説明欄から休憩時間を取得 const convert = str.replace(/[0-9]/gi, function(s) { return String.fromCharCode(s.charCodeAt(0) - 0xFEE0);});//全角数字を半角文字へ変換 switch(convert) { case '休憩:30分': rest += 0.5; break; case '休憩:1時間': rest += 1.0; } } //スタッフの月の給料を求める money += (day - rest)*1000 + night*1250; result[key] = {'day':day, 'night':night, 'rest':rest, 'money':money}; } return result; }View
ここでは行うのは、表をSpreadsheetへ作成することとSpreadsheetのバーに関数を登録することです。
GASでのコード
code.js//関数の登録 function onOpen() { const myMenu=[ {name: "スタッフを登録する", functionName: "addData"}, {name: "スタッフを削除する", functionName: "deleteData"}, {name: "勤務時間と給料の合計を出力", functionName: "resultController"} ]; SpreadsheetApp.getActiveSpreadsheet().addMenu("メニュー",myMenu); } //表の作成 function createResultTable(sheet, year, month, result) { sheet.getRange(2, 2).setValue(year+'年'+month+'月'); sheet.getRange(3, 2).setValue('スタッフ名'); sheet.getRange(3, 3).setValue('通常勤務時間'); sheet.getRange(3, 4).setValue('夜間勤務時間'); sheet.getRange(3, 5).setValue('休憩時間'); sheet.getRange(3, 6).setValue('今月の給料'); sheet.getRange(3, 7).setValue('※通常勤務・休憩:1000円/h、夜間勤務:1250円/1hで換算'); sheet.getRange(3, 2, 1, 5).setBackground('#fff2cc'); let i = 1; for(let key in result) { sheet.getRange(3+i, 2).setValue(key); sheet.getRange(3+i, 3).setValue(result[key]['day']); sheet.getRange(3+i, 4).setValue(result[key]['night']); sheet.getRange(3+i, 5).setValue(result[key]['rest']); sheet.getRange(3+i, 6).setValue(result[key]['money']); i++; } }Controller
ここではModelとViewの処理を結びつけます。(Controllerとして扱っても良いのかな?)
GASでのコード
code.js//DBの追加に関するController function addData() { const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('DB'); const info= getStaffInfo(); if(info == null) return; writeDB(sheet, info); } //DBの削除に関するController function deleteData() { const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('DB'); const name = getStaffName(); if(name == null) return; deleteDB(sheet, name); } //表の作成のController function resultController() { let box; //年と月を取得 box = getTime(); if(box == null) return;// const timeData = {'year':box[0], 'month':box[1]}; //DBシートからスタッフの勤務データを取得 const sheetDB = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('DB'); const staffData = getInfoFromDB(sheetDB); //記入の対象のシートを取得 const sheet = SpreadsheetApp.getActiveSheet(); //スタッフごとで勤務時間と給料の合計を取得 const result = getResult(staffData, timeData['year'], timeData['month']); //表の作成 createResultTable(sheet, timeData['year'], timeData['month'], result); }終わりに
LaravelなどでMVCを扱おうと思ってもそんなに気軽にできないけど、GASであれば手軽&実用的にMVC(っぽいもの)を扱えるので、バックエンドの学習の一環としてGASをやるのは面白いな~と思いました。
実際のバックエンドを触るのも面白いですが、今後もGASでWebアプリケーションとかを作ってみるなどして、お手軽疑似バックエンドで遊んでみようと思います。
- 投稿日:2020-02-24T19:11:05+09:00
javascriptのfor in(オブジェクトのプロパティ名についての繰り返し)
for-in文は、オブジェクトに存在するプロパティについて、プロパティの名前を順不同で取り出していき、処理を実行する構文です。
for in の書き方
for([プロパティ名を格納する変数] in [オブジェクト]) { 文 }指定したオブジェクトについて、プロパティ名を順不同で取り出し、inの前に定義した変数に格納してから処理を実行します。この時、繰り返しはすべてのプロパティについて処理を行うと終了します。
for in の例
var sum = 0; var obj = { a:1, b:2, c:3 }; for(var name in obj) { // オブジェクトの中のプロパティ名を取り出す。 sum += obj[name]; }参考
- 投稿日:2020-02-24T17:45:32+09:00
Node.js (サーバサイド JavaScript) 事始め
はじめに
TypeScriptを覚えたいのですが、まずはNode.jsの勉強から始めた方が良さそうな感じでしたので、Node.jsの勉強を始めました。
Noda.jsのインストール
公式サイトからインストーラーをダウンロードしてインストールします。
LTS版の方が無難だと思います。
Node.jsはmacやLinuxのイメージが強かったのですが、Windowsでも問題なく動きました。環境
OS:Windows 10 Pro 64bit
DB:SQL Server 2019(Cent OS 8 on Hyper-V)
node.js:v12.16.1
npm:v6.13.4
Editor:Visual Studio Code作業フォルダの作成
今回は「D:\Node」を作業フォルダにしました。
Hello World
作業フォルダに以下のファイルを作成します。
sample01.jsvar http = require('http'); var server = http.createServer( function(req, res) { res.writeHead( 200, {'Content-Type': 'text/plain'}); res.write('Hello World'); res.end(); }); server.listen(3000); console.log('サーバを起動しました');コマンドプロンプトを起動し、作業フォルダに移動します。
D: CD NodeNode.jsを起動します。
起動方法は「node ファイル名」ですnode sample01.js問題がなければ、コマンドプロンプトに以下の様に表示されます。
サーバを起動しましたhttp://localhost:3000
にアクセスします
ブラウザに「Hello World」と表示されればOKです。
- 投稿日:2020-02-24T17:03:11+09:00
Nuxt質問所、aLizサークルを開設しました!!
この度noteサークルを開設しました✨?
皆様の質問の、ぜ〜んぶに答えます!!!??こんな方にオススメです♪
・質問できるコミュニティがない…
・ググってみたけど分からない…
・やってみたもののエラーで動かない…
・初学者同士のコミュニケーションを取りたい!
・付け焼き刃の知識ではなく論理的に理解したい!?参加リンク
aLiz CLUB
https://note.com/aliz/circle?以下サークルの詳細です!
・Nuxtをメインとしたサービス
・Nuxt初心者のサポートが中心
・これからプログラミング始めようとしている方でも参加OK
・質問したり、ディスカッションをするのがメイン?aLizのポートフォリオ はこちら
https://note.com/aliz/n/nf1d2b5e16bc1#SsL5X?入りたい!
参加していただいた方を
aLizのSlackにご招待させていただきます。
noteサークル内の掲示板では
対応しておりませんのでご注意ください。
今後は、「Nuxt案件を受けたけど不安?」
そんな方のガッツリサポート版も作る予定です!
お楽しみに♪?サークルに入れないという方はこちら!
まずnote IDを作る必要があるので
そちらの手順を細かく解説してくれています。
https://note.com/kanoyuri/n/n97c0f4191bb5皆様のご参加
お待ちしております??
- 投稿日:2020-02-24T17:02:35+09:00
JavaScriptの「演算子」を全種類言えますか? ~演算子とは何かを完全に理解する~
JavaScriptプログラムの基本的な構成要素のひとつが演算子です。多くの方が普段のJavaScriptプログラミングで演算子を使っているでしょう。
では、あなたは「演算子とは何か」という問いに答えられますか? 演算子と演算子以外の違いはどこにあるのでしょうか?
演算子とは何かという定義は、人によって考え方が違うでしょう。筆者の個人的な考えとしては、演算子は「1つ以上の式から別の式を構成する構文を特徴づけるトークン」であると考えています。
しかし、JavaScriptには仕様書があります。仕様書はJavaScript (ECMAScript) に関する最も信頼できる情報源ですから、何が演算子で何が演算子でないのかについてもたいへん強力な基準を与えてくれることが期待できます。
そこで、この記事では何が仕様書で演算子と呼ばれているのかについて全て解説します。併せて、演算子っぽいけど演算子とは呼ばれていないものについても解説します。この記事を読んで、「JavaScriptの演算子とは何か」について正確な理解を得ましょう。
ついでに、折角ですからそれぞれの演算子に対して簡単な解説を挟みます。初心者の方はJavaScriptの演算子を網羅的に理解する機会とし、中級者以上の方はこの機会に演算子について復習しましょう。
なお、演算子は英語でoperatorと呼ばれます。この記事では、仕様書でoperatorと呼ばれている構文を出てくるのが早い順に全て列挙します。この記事で参照するのはDraft ECMA-262 / February 15, 2020 版です。
グルーピング演算子
( )
最初の演算子は
( )
です。これは(a + b) * 2
とかに出てくる( )
であり、計算の順序を定める効果を持ちます。
( )
は演算子に見えないという方がいるかもしれませんが、12.2.10 The Grouping OperatorでGrouping Operatorと呼ばれているので演算子に数えます。
new
演算子
new
は、new Array(10)
のような式に現れる演算子であり、クラス等のコンストラクタを呼びインスタンスを作る効果があります。この例の場合はlength
は10のArray
インスタンスとなります1。個人的にはこれが演算子というのは認めがたいのですが、仕様書が演算子であると言っているので演算子です(12.3.5 The new Operator)
インクリメント・デクリメント演算子
++
と--
です。これは文句なしに演算子ですね。12.4.4 Postfix Increment Operator~12.4.7 Prefix Decrement Operatorが該当します。仕様上は前置と後置が別々の演算子として定義されています。まあ構文が違いますから妥当ですね。
foo++
や++foo
はfoo
に1を足し、foo--
や--foo
は1を引きます。前置と後置の違いは返り値であり、前置の場合は計算の後のfoo
の値が、後置の場合は計算の前のfoo
の値が返ります。
delete
演算子12.5.3 The delete Operatorで定義されている演算子で、
delete foo
やdelete obj.foo
のような構文を持ちます。(すでにnew
が出ていますが)演算子といっても必ずしも記号ではないという例ですね。
delete
演算子は与えられた変数やプロパティを削除する演算子です。プロパティの削除
プロパティの削除は話が簡単です。
delete
演算子によって削除されたプロパティは文字通り存在しなくなります。次の例では最初obj
は{ foo: 123 }
でしたが、delete
の後はobj
は{}
と同じになります。const obj = { foo: 123 }; console.log(obj.foo); // 123 delete obj.foo; console.log(obj.foo); // undefined console.log(obj.hasOwnProperty("foo")); // falseただし、configurable属性がfalseに設定されたプロパティは削除できません。そのようなプロパティは
Object.defineProperty
などの手段で作ることができます(第3引数のオブジェクトでconfigurable: true
を指定しなければデフォルト値のfalse
となります)。const obj = {}; Object.defineProperty(obj, "foo", { value: 456 }); console.log(obj.foo); // 456 delete obj.foo; // エラーが発生(strictモードの場合)
configurable
がfalse
の場合は、プロパティは消えませんがエラーも起こりません。変数の削除
delete
演算子は変数も削除できます。しかし、皆さんがこの機能を使うのは非常にまれでしょう。なぜなら、strictモードではdelete
で変数を消すのは禁止されており、文法エラーとして扱われるからです。また、strictモードでなかったとしても、どんな変数でも消せるわけではありません。消すことができる変数はたいへん限られています。具体的には、消すことができるのはグローバル変数のみです。それも、
var
を使わずに宣言されたグローバル変数に限ります2。ちなみに、strictモードではvar
を使わずにグローバル変数を作ることはできない(存在しない変数にいきなり代入するとエラーが発生する)ので、strictモードではdelete
は無意味となります。この意味でも、delete
による変数の削除は非常に非strictモード的な機能であると言えます。// varを使わずに作られたグローバル変数 a = 123; // varを使って作られたグローバル変数 var b = 456; delete a; delete b; console.log(typeof a); // "undefined" (aが消えた) console.log(typeof b); // "number" (bは消えていない) function foo() { var abc = 123; console.log(abc); // 123 delete abc; // abc はグローバル変数ではないので消されない console.log(abc); // 123 } foo();ただし、
delete
が消すことができる変数はもう一つあります。それはeval
経由で作られた変数です。この場合は、var
で作られた変数であっても消すことができます。function foo() { eval("var abc = 123;"); console.log(abc); // 123 delete abc; console.log(abc); // abcが存在しないのでエラーが発生 }たとえ
eval
で作られた変数であっても、上の例のfoo
の中で作られた変数abc
はfoo
内のローカル変数となります。eval
経由で作られた変数の場合、特別にローカル変数をdelete
演算子で消すことができるのです。
delete
演算子の返り値
delete
演算子は式を作る構文であり、式ということは値があります。delete
演算子の返り値は真偽値であり、基本的には「削除に成功したらtrue
、失敗したらfalse
」です。ただし、最初から存在しない変数を削除しようとしていた場合はtrue
になるので要注意です。全く使わない演算子である割に何だか奥が深いですね。
void
演算子
void 式
という構文を持つ演算子で、式を評価するものの、結果を無視して常にundefined
を返します(12.5.4 The void Operator)。そんなもの何に使うのかと思いきや、実は
undefined
を得るという重要な使いみちがあります。undefined
を使いたいとき、void 0
と書けば3文字節約できます。また、単に文字数を節約するだけでなく、変数
undefined
が信頼できない場面でも有効です。というのも、我々はundefined
と書けばundefined
という値を得ることができますが、これはundefined
という名前のグローバル変数がundefined
が入った状態で用意されているからです。ということは、undefined
という名前の変数を作ってしまえばundefined
と書いてもundefined
が得られないのです。void
はこのような状況でもundefined
を供給することができます。{ const undefined = "hi"; console.log(undefined); // "hi" console.log(void 0); // undefined }例えば、TypeScriptで
??
演算子(左辺がnull
またはundefined
なら右辺を返し、それ以外なら左辺を返す)は次のようにトランスパイルされます。void 0
が使われていることが分かりますね3。// トランスパイル前 const foo = bar ?? 0; // トランスパイル後 const foo = (bar !== null && bar !== void 0 ? bar : 0);ちなみに、
null
は変数ではなくリテラルなので、null
と書けば常にnull
であることが保証されます。
typeof
演算子
typeof 式
とすると、その式
の値に応じていろいろな文字列が得られる演算子です(12.5.5 The typeof Operator)。プリミティブは細かに種類を判別することができますが、オブジェクトに関しては"object"
と"function"
の2種類の結果しかありません。プリミティブに対しては、"string"
,"number"
,"boolean"
,"bigint"
,"symbol
","undefined"
という種類の結果が得られます。比較的有名な話ですが、typeof null
は歴史的経緯により"object"
となります。これを"null"
にしようという試みもありましたが、頓挫しました。存在しない変数に対して
typeof 変数
としてもランタイムエラーではなく"undefined"
が得られることから、グローバル変数が存在するかどうかを判別する貴重な手段として重宝されています(これだとundefined
が入っているグローバル変数が存在している可能性も残りますが、それが問題になることはほとんどありません)。単項
+
演算子ここからはしばらく演算子らしい演算子が続きます。
12.5.6 Unary + Operatorで定義される単項
+
演算子は、+式
の形で用いることで与えられた式の値を数値に変換します。数値に変換するのは
Number(式)
でもできるのですが、短いプログラムが好きな人達には+式
が好まれています。これはTypeScriptでも認められており、TypeScriptでは+
に渡された値がnumber
型以外でも型エラーとはなりません(結果はnumber
型になります)。console.log(+"1e4"); // 10000 console.log(+true); // 1 console.log(+{ toString(){ return "-345" } }); // -345ちなみに、与えられる式がBigIntの場合は
+式
とNumber(式)
で違いがあります。console.log(Number(12345n)); // 12345 console.log(+12345n); // ランタイムエラーが発生これは、BigIntは
Number
という明示的な方法を使って数値に変換しなければいけないということです。そうなっている理由は、BigIntからNumberへの変換では数値の精度が落ちる可能性がありバグの元となるため、暗黙のうちにこれが行われるのは望ましくないからです。単項
-
演算子単項演算子(12.5.7 Unary - Operator)は
-式
という形で、式
の符号を反転させた数値を得る演算子です。-
はBigIntにも対応しており、符号が反転されたBigIntが返ります。一見対称性が高いように見える+
と-
ですが、BigIntに関してはこのような非対称性があるのです。const bignum = 12345n; console.log(-bignum); // -12345n console.log(+bignum); // ランタイムエラーBigInt以外が渡された場合は
+
と同様に全部数値(Number)に変換されます(ただし、オブジェクトをプリミティブ値に変換してBigIntになった場合はBigIntとして扱われます)。ちなみに、TypeScriptでは
-
演算子にnumber
型・bigint
型以外を与えるのはコンパイルエラーとなります。これも+
と非対称的な点ですね。
~
演算子ビット反転の演算子です(12.5.8 Bitwise NOT Operator ( ~ ))。一見何も説明する必要が無さそうなビット反転演算子ですが、JavaScriptでは一筋縄ではいきません。二つほど説明すべきことがあります。
一つは、数値(Number)のときは数値は32ビットとして扱われるということです。JavaScriptではそれ以上の数値も扱うことができますが、ビット演算の場合はそれ以上のビットは切り捨てられます。また、結果は符号あり32ビット整数として解釈されます。
console.log(~0); // -1 (0xffffffff は2の補数表現で-1を表したものであるため) console.log(~-1); // 0 console.log(~0x100000000); // -1 (32ビットを超えるので0と同様に扱われる)もう一つはBigIntの場合についてです。
~
演算子はBigIntも扱うことができますが、BigIntは任意精度の整数であるためビット数という概念がなく、「全てのビットを反転する」などということができないのです。そのため、BigIntのビット反転演算は別の方法で定義されています。6.1.6.2.2 BigInt::bitwiseNOT ( x )によれば、
x
がBigIntの場合「~x
の値は-x-1n
である」と定義されています。例えば、~123n
は-124n
となります。これは、通常の数値が32ビット整数に収まっている場合の挙動に一致しますから、それに倣ったものです。別の言い方をすれば、BigInt値を無限のビット数を持つ値であるとみなしてビット演算を行なっているとも言えます。
!
演算子これは言うことがあまりありません。与えられた式を真偽値に変換して真偽を反転させる演算子です(12.5.9 Logical NOT Operator ( ! ))。
true
とfalse
を逆にしたいときに使いましょう。また、値を真偽値に変換するのにBoolean(値)
ではなく!!値
として文字数を節約する人もいます。
**
演算子ES2016で密かに追加された演算子で、累乗を意味します(https://tc39.es/ecma262/#sec-exp-operator)。例えば
2 ** 3
は8
です。また、この演算子は右結合で、x ** y ** z
はx ** (y ** z)
として解釈されます。この演算子は構文に関して面白い点が1つほどあります。それは、
-2 ** 4
のような式が構文エラーとなることです。つまり、**
の左に単項+
演算子・単項-
演算子を使えないのです。これは、-2 ** 4
が(-2) ** 4
なのか-(2 ** 4)
なのか分かりにくいからです。エラーを避けるには、必要に応じてこのように括弧を補う必要があります。ECMAScriptの比較的新しい機能では、このように解釈が曖昧な構文ができそうな場合に、どちらかに寄せるのではなくどちらも許さないというデザインが取られることがあります。
**
はその先駆けのひとつです。
**
はNumberとBigIntのどちらにも対応していますが、BigIntは**
の右が負の数の場合にランタイムエラーとなります。**
の左が整数で右が負の数の場合結果が分数となり、BigIntでは表現できないからです。ちなみに、JavaScriptでは0の0乗は1です。
*
,/
,%
演算子乗法の演算子です。仕様書では12.7 Multiplicative Operatorsとして3種類まとめて定義されています。算術演算子全てに言えることですが、NumberとBigIntを混ぜて演算することはできません。両辺が同じ型である必要があります。
割り算に関しては、0で割った場合の挙動がNumberとBigIntで異なります。Numberの場合は左辺の値に応じてInfinityやNaNといった値が発生しますが、BigIntの場合は0で割るとランタイムエラーです。
このようにBigIntの計算はランタイムエラーが発生しやすいものになっています。これはNaNなどに起因する変なバグをなるべくわかりやすく防ぎたいという方向性と、そもそもBigIntにはNaNとかInfinityの概念がないという現実によるものです。
+
,-
演算子こちらは単項ではなく二項演算子の
+
・-
で、やはり仕様書では12.8 Additive Operatorsで定義されています。特に何の変哲もありませんが、+
は足し算だけでなく文字列の連結にも使われます。ビットシフト演算子
12.9 Bitwise Shift Operatorsで定義される3つの演算子
<<
,>>
,>>>
です。ビット演算なので、Numberの場合には32ビット符号付き整数として扱われるのも~
の場合と一緒です。ビットシフト演算子はNumberとBigIntで大きく扱いが異なる演算子で、似たような計算でもNumberとBigIntで異なる結果となります。console.log(1 << 33); // 2 console.log(1n << 33n); // 8589934592n console.log(4 << -1); // 0 console.log(4n << -1n); // 2n console.log(4 >> -2); // 0 console.log(4n >> -2n); // 16nBigIntの場合は話が単純で、ビットシフトはただ単に値に2の累乗数を左辺に掛けたり割ったりする計算となります。
一方Number型の場合は、まず右辺の数値(ビットシフトの距離を表す数値)が下位5ビットを取ることによって0〜31の整数に変換されます。
console.log(123 << 2); // 246 console.log(123 << 32); // 123 (32は0に変換されるのでビットシフトは起こらない)
>>
と>>>
の違いは左辺が負の数のときに現れます。>>
は符号を保ったままま右シフトが行われる一方、>>>
は符号を無視します。また、>>>
の結果は常に正の整数です。console.log(-5 >> 1); // -3 console.log(-5 >>> 1); // 2147483645 console.log(-5 >> 0); // -5 console.log(-5 >>> 0); // 4294967291
>>
と>>>
の挙動の違いは、最上位ビットの扱いと右ビットシフトにより空く部分をどうするかです。BigIntの場合は最上位ビットという概念がありませんが、これはBigIntでは>>>
をサポートしないという方法で解決します。console.log(-5n >> 1n); // -3n console.log(-5n >>> 1n); // ランタイムエラーが発生6種類の比較演算子
12.10 Relational Operatorsでは、
<
,>
,<=
,>=
,instanceof
,in
という6種類の演算子がRelational Operators (比較演算子)として紹介されています。最初の4種類はいいとして、残りの2つが異質ですね。正直、この6種類をひとまとめに扱う意味がよく分かりませんが、仕様がそう言っているのですからこれらは仲間なのです。とはいえ、まずは最初の4種類から見ていきます。
数値・文字列の比較演算子
<
,>
,<=
,>=
は、数値の比較だけでなく文字列同士の比較を行うことができます。両辺が文字列だった場合、文字同士をコードポイント4で比較することによる辞書順で大小判定が比較されます。文字列と数値の比較になった場合は数値に寄せられます。
instanceof
演算子
instanceof
演算子は演算子の中で最も長いものです。obj instanceof Class
で、obj
がClass
のインスタンスであるかどうかを判定して真偽値を返します。インスタンスであるかどうかの判定は、obj
のプロトタイプチェーンを辿ってコンストラクタにClass
を持つものが存在するかどうかを調べることで行われます。ただし、実は
instanceof
の挙動はクラス側の[Symbol.hasInstance]
メソッドで制御できます。class Foo { static [Symbol.hasInstance](obj) { return obj.foo === 100; } } const obj = { foo: 0 }; console.log(obj instanceof Foo); // false obj.foo = 100; console.log(obj instanceof Foo); // true
in
演算子
in
演算子はオブジェクトが指定した名前のプロパティを持つかどうか判定して真偽値を返すプロパティです。Object.prototype.hasOwnProperty
とは異なり、プロトタイプチェーンを辿ってプロパティがあるかどうか判定します。console.log("hasOwnProperty" in {}); // true等値演算子
等価演算子は、12.11 Equality Operatorsで定義されている
==
,!=
,===
,!==
の4種類の演算子です。日本語で何と呼べば分からない演算子第一位(当社比)です。基本的には
===
,!==
だけ使っていれば事足りる場面がほとんどで、==
や!=
は避けるべきです。これは、==
は両辺の型が違う場合に型変換によって型を揃えて比較しようとするからです。詳しいことはこちらの既存記事をご参照ください。ビット演算の二項演算子
ビット演算の二項演算子は12.12 Binary Bitwise Operatorsで定義されています。もうすこし短い訳があればいいのですが、「ビット演算の二項演算子」というのはいささか直訳的ですね。
これらもビット演算なので、Numberの場合は32ビット整数に変換の上ビット演算が行われる一方、BigIntの場合はビット数の制限がありません。
console.log((2 ** 32) | 1); // 1 console.log((2n ** 32n) | 1n); // 4294967297n二項論理演算子
二項論理演子と言われて何のことかお分かりでしょうか。これは12.13 Binary Logical Operatorsの直訳であり、
||
と&&
のことです。また、ES2020からは??
が追加されました。??
はプロポーザル時代はNullish Coalescing Operator(nullish合体演算子)と呼ばれていましたが、仕様書にはこの用語は出てきません。x ?? y
という式はCoalesceExpressionと呼ばれているので、Coalescing OperatorとかCoalesce Operatorと呼ぶのはありかもしれません。意味は今さらなので省略しますが、これらに共通する特徴は短絡評価です。つまり、左辺を先に評価して、結果が左辺となることが分かったなら右辺は評価されません。
結合の優先順位は
&&
>||
>??
ですが、??
の右に||
や&&
が来ることはできません。w && x || y ?? z // OK ((w && x) || y) ?? z と同じ意味 w && x ?? y || z // 構文エラーが発生 (w && x ?? y) || z // OK ((w && x) ?? y) || z と同じ意味これは、
||
や??
は左辺の値が偽(あるいはnullish)の値だったときのフォールバックとして付加されることが多いところ、x ?? y || z
としたときの挙動が分かりにくいことによる処置です。条件演算子
条件演算子はオペランドを3つ持つ唯一の演算子であり、それゆえ三項演算子と呼ばれることもあるやつです。仕様書を見れば、正式名称が条件演算子 (Conditional Operator) であることは一目瞭然ですね(12.14 Conditional Operator ( ? : ))。
代入演算子
代入演算子は
=
,+=
,-=
,*=
,/=
,%=
,<<=
,>>=
,>>>=
,&=
,|=
,^=
,**=
の総称です()。
&&=
や||=
は存在しませんが、これらを追加するというプロポーザルは存在します。2020年2月現在ではStage 2です。これらは+=
などとは多少意味が違う点があります。具体的には、a += b
はa = a + b
と同じである一方、a ||= b
はa || (a = b)
と同じ意味になります。つまり、a
が真なら代入すら行われないのです。このことは、セッタによって代入に副作用が仕込まれていた場合に目に見える違いとなって現れるのです。コンマ演算子
JavaScriptでは
,
は色々な場面で使われますが、実は演算子としての,
も存在します。それがコンマ演算子(12.16 Comma Operator ( , ))です。コンマ演算子は一番結合度の低い演算子となっています。
x, y
という式は、x
とy
を順に評価してy
の値を返します。x
は評価こそされるもののその結果は使用されません。これは、何だかvoid
演算子と似ているところがあります。コンマ演算子を我々が普段使うことはほとんどありませんが、適当なminifierを用いてソースコードを最小化するとコンマ演算子が結構使われているのを見ることができます。無理やりコンマ演算子を使ってみるとこんな感じです。
for (let i=0; i < 10; i++) console.log("i is"), console.log(i);
for
文のボディ部分は{ }
を使わなければ書ける文はひとつだけですが、コンマ演算子を用いると複数の関数呼び出しを一つの式で表すことができます。一つの式で表すことができればそれは式文というひとつの文になりますから、{ }
無しでfor
文の中に複数の関数呼び出しを書くことができるのです。こうすると、{ }
で関数呼び出しを並べるよりも文字数を2文字ほど省略できます。たいへん嬉しいですね。コンマは他の構文要素でも使われる記号ですから、コンマ演算子が活躍できる場所は限られています。例えば、関数呼び出しの引数リストの中ではコンマ演算子は使えません。
,
は引数の区切りとして扱われるからです。無理やりコンマ演算子を使うには、( )
を使ってひとつの式であると明示する必要があります。f(x, y); // (x, y)というひとつの式を渡しているのではなくxとyという2つの式を渡している f((x, y)); // (x, y)というひとつの式を渡している以上でECMAScriptで演算子 (operator) と呼ばれているものを全て列挙できました。
演算子なのかどうか微妙なもの
仕様書はJavaScriptという言語を定める絶対的な基準となる文書ですが、人間が作るものですからミスもあります。仕様書には、演算子でないと思われるものを演算子と呼んでいるように見える箇所が2箇所あります。折角ですから、この記事で触れておきます。
super
Table 7: Additional Essential Internal Methods of Function Objectsは関数オブジェクトが持つ内部スロットを定義しています(内部スロットについてはJavaScriptの「継承」はどう定義されるのか? 仕様書を読んで理解するで詳しく解説しています)。その2行目、[[Construct]]内部メソッドの説明に次のように書かれています(強調は筆者)。
Creates an object. Invoked via the new or super operators.
「
new
またはsuper
演算子」と書かれており、super
が演算子であるかのような記述があります。一方、super
は仕様書の12.3.7 The super Keywordで定義されており、こちらでは「super
キーワード」と呼ばれていますからsuper
は演算子でないと思われます。ちなみに、
super
はクラス宣言の中で使うことができる構文であり、コンストラクタの中のsuper()
で親クラスのコンストラクタを呼び出したり、super.foo
で親クラスのプロパティ・メソッドを参照することができます。Optional Chaining
Optional ChainingはES2020で追加された
?.
という構文で、foo?.bar
はfoo.bar
と似た動作をしますが、foo
がnull
やundefined
のときはエラーにならずに結果がundefined
となります。Optional Chainingについては以下の記事で詳しく解説しています。仕様書では
?.
による構文はOptional Chainとだけ呼ばれており(12.3.9 Optional Chains)、演算子とはされていません。しかしながら、一箇所だけ?.
を演算子と呼んでいる箇所があります。それは12.3.1.1 Static Semantics: Early Errorsです。NOTEの中に次のような記述があります(強調は筆者)。...so that it would be interpreted as two valid statements. The purpose is to maintain consistency with similar code without optional chaining operator:
foo.bar
の.
は演算子とはどこにも書かれていませんから、そこから類推すると?.
が演算子とは考えにくいですね。上記の記述はミスであると考えられます。演算子ではないもの
ここまでで触れられなかったものは、仕様書でまったく演算子と呼ばれていないので明らかに演算子ではありません。どのようなものが演算子でないのか確認しましょう。
関数呼び出しの
( )
func(1, 2, 3)
のような関数呼び出しに出てくる( )
は演算子ではありません。個人的にはnew Class(1, 2, 3)
のnew
が演算子であることを考えると少し違和感がありますが、我々ごときが感じる違和感など仕様書の前には塵ひとつほどの価値しかありません。プロパティアクセスの
.
と?.
?.
は先ほど見たように一箇所だけ演算子と呼ばれているため微妙ですが、.
についてはまったく演算子と呼ばれていません。各種リテラル
次の式にあるような
{ }
や[ ]
は演算子ではなく、リテラルと呼ばれています。const obj = { foo: 0 }; const arr = [1, 2, 3];括弧だから演算子ではないというわけでもないのが難しいところです。最初に見たように
( )
は演算子でした。
yield
・await
これらは
yield 式
やawait 式
という形で使うことができる式であり、一見すると演算子でもおかしくありません(構造としてはtypeof 式
などと全く同じです)。しかし、仕様ではこれらは演算子とはされず、YieldExpressionやAwaitExpressionのように定義されていますから、「yield式」や「await式」と呼ぶのが正しそうです。
スプレッド構文・レスト構文の
...
ES2015以降、JavaScriptには
...
という構文がたびたび登場します。...
はスプレッド構文とレスト構文に大別され、前者は配列リテラルやオブジェクトリテラルの中、そして関数呼び出しの引数リストの中で使われる一方、後者は分割代入の配列・オブジェクトパターンや、関数宣言時の引数リストで使われます。
...
は「スプレッド演算子」のように呼ぶ人が多くいるものの、仕様書では全く演算子と呼ばれていません。...
を演算子と呼んでいた方はぜひ今日から改めましょう。これが演算子ではない理由は、「式を作らない」からであると推察しています。すなわち、
...
は配列リテラルなど、他の構文の一部としてしか使うことができず、単独で式をなすものではないのです。const arr = [...foo]; // ←これはOK const arr = ...foo; // ←こんな構文は無い変数宣言の
=
先ほど解説した通り
=
は代入演算子の一種ですが、実は変数宣言のときの=
は厳密には代入演算子ではありません。let foo = 123; // ←この = は代入演算子ではない foo = 456; // ←この = は代入演算子前者は変数宣言の構文の一部(Initializer)として、代入演算子とは別個に定義されているのです。また、関数引数などのデフォルト値の定義にも
=
が使われますが、こちらもInitializerであり代入演算子ではありません。実際に仕様書を見ると、Initializerの定義には次のよう書かれています(画像で引用)。
一方で、代入演算子の
=
はAssignmentExpressionの定義の中で定義されています(画像で引用)。この2つを見ると、それぞれに
=
が使われており、同じ=
という記号でも出自を異にするものであることが分かります。我々が普段=
を使う際はそんなことをいちいち気にしなくても何とかなってしまうのですが。まとめ
この記事ではECMAScriptに存在する「演算子」を全種類(ES2020時点)解説しました。併せて、演算子っぽいが演算子ではないものも解説しました。
結局演算子と演算子でないものの違いは何なのかという問いについては、残念ながら単純明快な答えはありません。演算子は必ず式を作るという点は間違い無さそうですが、式を作るなら何でも演算子というわけではありません(
foo.bar
の.
やawait
式のawait
は演算子ではありませんでしたね)。演算子であると定義されているものが演算子であると言うほか無いのです。いずれにせよ、どれが演算子でどれが演算子でないかを丸暗記することに意味はありません。プログラムを書く身としては、どんな方法でもいいのでJavaScriptの構文を理解し、使いこなせるようになることが重要なのです。
その一方で、仕様書はJavaScriptプログラミングにおいて唯一完全に信頼できる情報源であり、質の高いプログラムを書くにあたって仕様書が果たす役割は小さくありません。ですから、JavaScriptの解説記事を書くような場合には演算子とそれ以外の区別ができていない(=仕様書を理解していない)と非常に信頼性が低くなります。皆さんもJavaScriptの解説記事を書くときはぜひ正確性に気をつけましょう。また、JavaScriptの解説記事を読む際も演算子をひとつの試金石としてみるのはいかがでしょうか。
配列の場合は実際にはただのインスタンスではなくexotic objectが作られるのでちょっと特殊ですが。 ↩
関数の中ではないトップレベルのコードにおいては、
var
で宣言された変数はグローバル変数として扱われます。一方、let
やconst
で宣言された変数はグローバル変数として扱われません。 ↩
null
またはundefined
であることを判定するならbar != null
でいいのではと思われたかもしれませんが、!=
を使うとdocument.allがdocument.all == null
と判定されてしまうのでまずいのです。 ↩JavaScriptの文字列はUTF-16でエンコーディングされているため正確にはコードユニットですが。 ↩
- 投稿日:2020-02-24T16:28:57+09:00
PlayCanvasでサンディちゃんを歩かせるゲームを作るぞ☆(第6回)~他エンティティのイベントを拾う~
こんなん作ってます。
https://playcanv.as/b/iAPwWXqY/
※本解説よりも開発が進んでいることもございますのでご了承ください。一つのエンティティの衝突判定をUI用のエンティティに伝えるにはどうする?
PlayCanvasは各エンティティにスクリプトを持たせてプログラミングされますが、円柱とUIは違うエンティティです。
どうやってエンティティ間でやりとりをしたらよいのでしょうか?
答えはユーザマニュアル>スクリプティング>通信にありました。
https://developer.playcanvas.com/ja/user-manual/scripting/communication/通信ってのはなかなか聞かない表現かもしれませんが、他のエンティティのイベントを拾ってくることだと考えてください。
スクリプト間の通信には
- スクリプト属性で参照づけられているエンティティのみから拾ってくるイベント
- メインアプリケーション(this.app)を中央ハブとして、全てのエンティティから拾ってこれるアプリケーションイベント
の2種類があります。
どうみても後者が便利です(笑)エンティティにサンディちゃんが衝突した際にUI部品へ衝突判定を伝えるよう、アプリケーションイベントを使ってプログラミングしていきます。
衝突判定
以下のスクリプトを作成します。
hitSandy.jsvar HitSandy = pc.createScript('hitSandy'); // initialize code called once per entity HitSandy.prototype.initialize = function() { this.entity.collision.on('triggerenter', this.onTriggerEnter, this); }; // update code called every frame HitSandy.prototype.update = function(dt) { }; HitSandy.prototype.onTriggerEnter = function(entity) { this.app.fire('hitSandy:hit', this.entity.name, entity.name); };このスクリプトが適用されているエンティティの当たり判定に、何かしら剛体が衝突した際に「hitSandy:hit」というイベントを発火するようにしました。
ちなみに「hitSandy:hit」イベントを受け取った側は衝突した2つのエンティティの名前を受け取ることができます。
このスクリプトをPlayCanvasでサンディちゃんを歩かせるゲームを作るぞ☆(第4回)で作成したtemplatesディレクトリ内の「start」「goal」エンティティに適用してください。
衝突判定をUIで表示する
イベントの通知を受け取るスクリプトの初期化関数でイベントの受け取りと、イベントを受けたときのイベントハンドラを作成。
今回の場合、当たり判定の衝突を検知したら「onSandyHit」関数をコールする。
衝突イベントからは衝突した2つのエンティティの名前も届いて、「onSandyHit」関数ではname1,name2の引数がそれを受け取るようになっています。ui.js// initialize code called once per entity Ui.prototype.initialize = function() { ・・・ // initializeメソッドに以下を追加 // listen for the player:move event this.app.on('hitSandy:hit', this.onSandyHit); // remove player:move event listeners when script destroyed this.on('destroy', function() { this.app.off('hitSandy:hit', this,onSandyHit); }); } //onSandyHitメソッドを新規追加 Ui.prototype.onSandyHit = function(name1,name2) { // 第1引数は衝突された側のエンティティの名前、第2引数は衝突したエンティティの名前 // メッセージを作成。 var caption = ""; var message = ""; if(name1=="start"){ caption = "START"; message = name2 + " start!!"; }else if(name1 == "goal"){ caption = "GOAL"; message = name2 + " reach the goal!!"; } // ダイアログにメッセージを載せる var disp_dialog = document.getElementsByClassName('pos_top_center')[0]; disp_dialog.style.display = "block"; disp_dialog.getElementsByClassName('caption')[0].innerHTML = caption; disp_dialog.getElementsByClassName('message')[0].innerHTML = message; };これでLaunchしてみましょう。
- 投稿日:2020-02-24T16:25:56+09:00
javascript(node.js)でのsubstring
- 投稿日:2020-02-24T16:16:49+09:00
javascript(node.js)のsetHeaderとwriteHeadの違い
- 投稿日:2020-02-24T15:47:52+09:00
TweenMaxで「0→任意の値→0」という値の変化をつくる
TweenMaxで「0→任意の値→0」という値の変化をつくる
hoge.jsvar obj = { propA: 0, } var forth = 10; var tm = TweenMax.to(obj, 5, { onUpdate : function(){ //進捗を0-1の値で返すtweenmaxのメソッド var progress = this.progress(); //三角関数で0から180度のとき0→1→0を返すことを利用 var num = Math.sin(progress * 180 * Math.PI / 180); //強度の値をかけることで、任意の時間に「0→任意の値→0」という値の変化をつくる var final = num * forth; final = Math.round(final * 100)/ 100; obj.propA = final; console.log(obj.propA); } });もっと簡単な方法がある気がするす
toを続けて書く...?しかし任意のイージングのなかで変化させたい...ゆえにこのように記述しています
- 投稿日:2020-02-24T15:30:41+09:00
webpack入門して仲良くなりたい~Plugin編~
仲良くなりたい
今回の記事ではBabelとPluginについて書きます。
Babel
突然出てきたBabel…
まずは、役割の確認を行います。Babel is a JavaScript compiler
Babel is a toolchain that is mainly used to convert ECMAScript 2015+ code into a backwards compatible version of JavaScript in current and older browsers or environments. Here are the main things Babel can do for you:引用: https://babeljs.io/docs/en/
要するに、最新のJavaScriptの記述を汎用的に使えるものに変換(トランスパイル)してくれるみたいです。
// アロー関数(変換前) [1, 2, 3].map((n) => n + 1); // 変換後 [1, 2, 3].map(function(n) { return n + 1; });これを導入することで、開発時は最新の記法を使用するがブラウザに反映させるファイルは変換しているので、ファイルの考慮がいらないというわけです。
Babel導入
babel
それでは早速、導入していきます。
必要なパッケージを入れます。npm i -D @babel/core@7.8.4 babel-loader@8.0.6 npm i -S @babel/cli@7.8.4 @babel/preset-env@7.8.4ちなみに前回の記事で紹介した
ts-loader
を使えばTypeScriptをES5にコンパイルしてくれるっぽいです。ここの章はBabelの基本的な動作が見たいのでJavaScriptで記述します。babel-core
babelの本体です。
babel-loader
おなじみのローダーですね。
バンドルが実行される前にbabelを作動させるためのloaderですbabel-preset-env
トランスパイルするためのパッケージです。
webpack.config.jsの設定追加
webpack.config.jsconst path = require("path"); const outputPath = `${__dirname}/dist`; // モジュールどうやって出力するか記述します module.exports = { // 実行環境設定 mode: "development", // エントリー ポイントを決めます entry: "./src/app.js", //js output: { // どこにバンドルファイルを出力するか決まます path: outputPath, // 出力先のファイル名を決めます filename: "bundle.js" }, // loaderの設定 module: { rules: [ { test: /\.js$/, exclude: /node_modules/, loader: "babel-loader" } ] }, devServer: { contentBase: outputPath }, // bundle.jsのコードを見やすくする devtool: "inline-source-map" };.babelrc
このファイルでは、babelのコンパイル方の設定を行います。
{ "presets": ["@babel/preset-env"] }トランスパイル実行
今回バンドルするファイルはアロー関数を記述したものを使います。
allow.jsexport const allow = array => { var mappedArray = array.map(n => n + 1); return mappedArray; };buildを実行して、Babelによってトランスパイルされたファイル部分を見てみます
bundle.js/***/ "./src/allow.js": /*!**********************!*\ !*** ./src/allow.js ***! \**********************/ /*! exports provided: allow */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "allow", function() { return allow; }); var allow = function allow(array) { var mappedArray = array.map(function (n) { return n + 1; }); return mappedArray; }; /***/ }),なんだかよさげですね!
html出力を自動化
今までは、distフォルダ内の
index.html
を事前に用意していましたが、これもビルド時に自動生成するようにします。html-webpack-plugin
ここでプラグインのを使ってこの問題の解決を目指します。プラグインとはwebpackを拡張するものと考えてよさそうです。
htmlファイルの自動生成にはhtnl-webpack-plugin
が必要なので早速導入します。npm install -D html-webpack-plugin@3.2.0次に
webpack.config.js
の編集を行います。webpack.config.jsconst path = require("path"); const outputPath = `${__dirname}/dist`; // プラグインの読み込み const HtmlWebpackPlugin = require("html-webpack-plugin"); module.exports = { mode: "development", entry: "./src/app.js", output: { path: outputPath, filename: "bundle.js" }, module: { rules: [ { test: /\.js$/, exclude: /node_modules/, loader: "babel-loader" } ] }, devServer: { contentBase: outputPath }, devtool: "inline-source-map", // ここを追加 plugins: [ new HtmlWebpackPlugin({ template: "./src/index.html" }) ] };plugins
ここのプロパティは配列で、使用するプラグインを追加していきます。
ここまで編集が終えたら実行してみてください。
distフォルダ内にbundle.js
とindex.html
が出力されているはずです。最後に
ここまで、webpackを使っていろいろ遊んでみました。
まだまだ、多くのLoaderやPluginが存在します。
ですが、これだけ遊んだら基本的なことがわかっているので調査も身近なものに感じると考えています。次は、React入門して仲良くなりたいと思っています。
- 投稿日:2020-02-24T15:10:10+09:00
Three.jsの超基本2
前回の記事に引き続き、学習した内容のド忘れ防止のためにThree.jsの基本的な使い方をまとめます。
前回の記事はこちら -> Three.jsの超基本
Group(グループ)
複数のMesh(物体)を、まとめて管理できる。
Group化すると、複数の物体を同時に動かすことができる。(function() { var scene; var head; var body; var group; // ステージを追加 scene = new THREE.Scene(); // 物体を作成 head = new THREE.Mesh( new THREE.BoxGeometry(20, 20, 20), new THREE.MeshLambertMaterial({ color: 0xff0000 }) ); head.position.set(0, 40, 0); body = new THREE.Mesh( new THREE.BoxGeometry(40, 60, 40), new THREE.MeshLambertMaterial({ color: 0xff0000 }) ); body.position.set(0, 0, 0); // グループを作成し、headとbodyを追加 group = new THREE.Group(); group.add(head); group.add(body); // ステージにグループを追加 scene.add(grooup) })();OrbitControls(カメラを制御)
カメラを自動で動かしたり、マウス操作でカメラを動かすことができます。
マウス操作でカメラを動かす
(function() { var stage = document.getElementById('stage'); var controls; // カメラコントローラを作成(カメラ, DomElement) controls = new THREE.OrbitControls(camera, stage); })();自動でカメラを動かす
(function() { var stage = document.getElementById('stage'); var controls; controls = new THREE.OrbitControls(camera, stage); // カメラの自動回転 controls.autoRotate = true; // 回転スピードの調整 controls.autoRotateSpeed = 8.0; function render() { // 60fpsの間隔で繰り返し処理を実行(描画する) requestAnimationFrame(render); // カメラを再描画 controls.update(); // 描画する renderer.render(scene, camera); } render(); })();Shadow(影をつける)
光源に対して物体の影を、別の物体に落とすことができる。
(function() { // 影を有効化 renderer.shadowMap.enabled = true; // ライトの影表示を有効化 light.castShadow = true; // 物体の影を有効化 box.castShadow = true; // 影を受ける設定を有効化 plane.receiveShadow = true; // カメラの領域設定 light.shadow.camera.position.set(0, 100, 0); light.shadow.camera.left = -200; light.shadow.camera.right = 200; light.shadow.camera.top = 200; light.shadow.camera.bottom = -200; })();カメラヘルパーで領域を可視化する
var shadowHelper = new THREE.CameraHelper(light.shadow.camera); scene.add(shadowHelper);Texture(テクスチャを物体に貼り付ける)
作成した物体に、画像を貼り付けることができる。
※サーバーを立てずに行うと、クロスドメイン系のエラーが発生する場合がある。(function() { var loader = new THREE.TextureLoader(); // 先にテクスチャを読み込んでから物体を生成する loader.load('img/logo.png', function(texture) { createBox(texture); render(); }); function createBox(texture) { box = new THREE.Mesh( new THREE.BoxGeometry(40, 40, 40), // テクスチャを貼り付ける際は「map」を使う new THREE.MeshBasicMaterial({ map: texture }) ); box.position.set(0, 0, 0); scene.add(box); } })();Font(3Dテキストを描画する)
指定したフォントで、3Dテキストを描画することができる。
(function() { var loader = new THREE.TextureLoader(); // フォントファイルを読み込んだ後に物体を生成する loader.load('js/helvetiker_regular.typeface.json', function(font) { createText(font); render(); }); function createText(font) { text = new THREE.Mesh( new THREE.TextGeometry('SampleText!!', { // フォントを指定 font: font, // フォントサイズを指定 size: 24, // フォントの厚みを指定 height: 4 }), // 「side: THREE.DoubleSide」で両面を描画する new THREE.MeshBasicMaterial({ color: 0xf39800, side: THREE.DoubleSide }) ); text.position.set(-100, 0, 0); scene.add(text); } })();
- 投稿日:2020-02-24T14:06:56+09:00
Firebase 条件分岐をしてデータの読み取り(Firestore)
はじめに
FirebaseのデータベースFirestoreで、条件分岐をしてからデータを取得する例です。
また、取得したデータの数をカウントしてます。私の環境
・Ionic + Angularwhereを使う
eventの中身がこんな感じだとする。
event.model.tsexport class Event { constructor( public id?: string, public title?: string, public periods?: boolean, ) {} }home.page.tsimport { AngularFireAuth } from '@angular/fire/auth'; import { AngularFirestore } from '@angular/fire/firestore'; @Component({ selector: 'app-home', templateUrl: 'home.page.html', styleUrls: ['home.page.scss'] }) export class HomePage implements OnInit { private eventCount; private periodCount; constructor( private afAuth: AngularFireAuth, private db: AngularFirestore ) { } ngOnInit() { this.afAuth.auth.onAuthStateChanged((user) => { if (user != null) { // events以下の全てのドキュメントを取得してカウント this.db .collection(`users/${user.uid}/events`) .snapshotChanges() .subscribe(c => { this.eventCount = c.length; }); // events以下のドキュメントでperiodsプロパティがtrueのドキュメントを取得 this.db .collection(`users/${user.uid}/events`, ref => ref.where('periods', '==', true) ) .snapshotChanges() .subscribe(c => { this.periodCount = c.length; }); } }); }参考
}
- 投稿日:2020-02-24T12:10:46+09:00
JavaScript 演算子⓵ 四則演算+?
はじめに
この記事では、四則演算や余剰(余り)に使用する演算子について解説します。
四則演算
sample.jslet num; let string; num = 5 + 3; //変数numに5と3の和を代入する string = "ES" + "6"; //変数stringに"ES"と"6"を連結した文字列を代入する string = "ES" + 6; //変数stringに"ES"と6を連結した文字列を代入する num = 7 - 2; //変数numに7から2を引いた値を代入する num = 4 * 5; //変数numに4と5の積を代入する num = 6 / 2; //変数numに6で2を割った値を代入する演算子はそれぞれ役割があります。
+
数値の加算、文字列の連結
-
数値の減算
*
数値の乗算
/
数値の除算余剰、べき乗、その他
sample.jslet num; num = 5 % 2; //変数numに5で2を割った余りの値を代入する num = 10 ** 2; //変数numに10の2乗を代入する num = 10++; //変数numに10を代入し、10+1 num = ++10; //変数numに10+1を代入する num++; //変数num+1 num = 6--; //変数numに6を代入し、6-1 num = ++6; //変数numに6-1を代入する num--; //変数num-1 let string = "3"; let num2 = 5; num = +string; //変数numに、数値の変数stringを代入する num = -num2; //変数numに、負の値の変数num2を代入するこれらの演算子の役割は、以下の通りです。
%
数値を割った余り(余剰)
**
数値のべき乗
++
数値に+1 前置記法なら先に+1して値を返し、後置記法なら値を返してから+1
--
数値に-1 ++と同じく、前置記法と後置記法がある
+
数値を正の数にする 文字列でも可能
-
数値を負の数にする 文字列でも可能最後に
⓶では、型を調べる演算子をとりあげようと思います。
- 投稿日:2020-02-24T04:57:14+09:00
初心者によるプログラミング学習ログ 247日目
100日チャレンジの247日目
twitterの100日チャレンジ#タグ、#100DaysOfCode実施中です。
すでに100日超えましたが、継続。
100日チャレンジは、ぱぺまぺの中ではプログラミングに限らず継続学習のために使っています。
247日目は、
おはようございます
— ぱぺまぺ@webエンジニアを目指したい社畜 (@yudapinokio) February 23, 2020
247日目
・youtubeで、Jsのアコーディオンメニュー模写
・Udemyで、Js講座#早起きチャレンジ#駆け出しエンジニアと繋がりたい#100DaysOfCode
- 投稿日:2020-02-24T02:57:30+09:00
JavaScriptのプロトタイプチェーンを深堀りする
プロトタイプチェーンとは
オブジェクトが継承元プロトタイプを参照し、そのプロトタイプがまたその継承元プロトタイプを参照し.....のような連鎖のことをプロタイプチェーンといいます。
プロタイプチェーンの仕組み
1. 指定したオブジェクトでプロパティの存在を調べる
2. なかった場合__proto__
が参照する先で存在を調べる
3. それでもなかった場合__proto__
が参照する・・・(ループ)
4. 最終的にnullになるまで行う。nullならundefined
を返す配列のプロトタイプチェーン
図で理解するJavaScriptのプロトタイプチェーン - Qiitaで説明されていた配列のプロトタイプチェーンについて補足・解説します。
※以下のコード及び画像は記事内より引用です。配列を生成① var myArray = []; ② myArray.push("hoge"); // これはプロトタイプチェーン先のメソッドから呼べる ③ myArray.isArray(); // undefined()を実行しているようなもの ④ Array.isArray(myArray); // true isArrayの使い方はこう① 配列の定義
空の配列を定義① var myArray = [];これは
myArray
配列を定義、つまりmyArrayインスタンス
を生成している以下の記述と同義です。Arrayオブジェクトのインスタンス化var myArray = new Array();
それと同時に、コンストラクタ関数である
Arrayオブジェクト
のprototype
が、myArray.__proto__
に代入されます。__proto__は全てのオブジェクトが持つ、内部プロパティ。そのオブジェクト自体のprototypeへの参照を持つ。
同時に起こるmyArray.__proto__ = Array.prototype; // Arrayオブジェクトのプロトタイプを代入② pushメソッドはどこにある?
配列に要素を追加するpushメソッド② myArray.push("hoge");myArrayオブジェクト自体には
pushメソッド
は存在しません。
オブジェクトにプロパティが存在しない場合、プロトタイプチェーンの仕組みにより、そのオブジェクトの__proto__
が参照する先でプロパティの存在有無を確認します。①より
myArray.__proto__ === Array.prototype;
なので、myArray
はArray.prototype
が持つpushメソッド
を参照することができます。myArrayがpushメソッドを使える理由myArray.push("hoge"); // myArrayオブジェクトにはpushメソッドは存在しない。 ↓ // myArray.__proto__を参照する。 myArray.__proto__ === Array.prototype // true ↓ // Array.prototypeにpushメソッドがないか確認する。 Array.prototype.push // Array.prototypeにはpushメソッドが存在するので、myArrayはpushメソッドを使える。③ myArray.isArray()はなぜエラーになる?
なぜundefined?③ myArray.isArray(); // undefined()を実行しているようなもの
isArray()
とは、コンストラクタ関数Array
が持つメソッドの一つです。
myArray
はそのプロトタイプを含めてisArray()
を所持していないので、③式はundefined
になります。myArrayインスタンスはisArrayメソッドを参照できないmyArray.isArray(); // myArrayオブジェクトにはisArrayメソッドは存在しない。 ↓ myArray.__proto__ === Array.prototype // myArray.__protoを参照するが、Array.prototypeにもisArray()は存在しない。 ↓ Array.prototype.__proto__ === Object.prototype // Array.prototype.__protoを参照するが、Object.prototypeにもisArray()は存在しない。 ↓ Object.prototype.__proto__ === null // Object.prototype.__proto__はnullなので③式はundefinedとなる。ほぼ全てのオブジェクトは
Object.prototype
を始祖に持ち1、Object.prototype
がプロトタイプチェーンの終端となります。④ isArray()を正しく使うには?
インスタンスは
constructorプロパティ
を持っています。
そしてそのconstructorプロパティは、インスタンス化の元となったオブジェクト(継承元)のコンストラクタ関数を参照します。つまり、
myArray.constructor
はその元となったオブジェクトであるArray
を参照しています。myArrayのconstructorプロパティはArrayオブジェクトを参照するmyArray.constructor === Array; // trueArrayオブジェクトは
isArrayメソッド
を所持しているので、isArrayの正しい使い方は以下になります。isArray()を正しく使うパターン④ Array.isArray(myArray); // true myArray.constructor.isArray(myArray); // true Array.prototype.constructor.isArray(myArray); // true以上が配列を定義した際のプロトタイプチェーン解説です。
Objectオブジェクトのプロトタイプチェーン
すべてのオブジェクトは
__proto__プロパティ
を持つので、それはObjectオブジェクト
も同様です。
プロトタイプチェーンの末端であるObject.prototype
を参照するのかと思いきや、以下記述はfalseになります。Object.__proto__の参照元はObject.prototypeではないObject.__proto__ === Object.prototype // falseそれでは
Object.__proto__
は何を参照しているのでしょうか?答えは
Function.prototype
です。Object.__proto__の参照元はFunction.prototypeObject.__proto__ === Function.prototype // true
Function.prototype
は、Object.prototype
を参照することでプロトタイプチェーンの末端にたどり着きます。ObjectオブジェクトのプロトタイプチェーンObject.__proto__ === Function.prototype // true ↓ Function.prototype.__proto__ === Object.prototype; // true ↓ Object.prototype.__proto__ === null // trueFunctionオブジェクトのプロトタイプチェーン
Functionオブジェクト
の__proto__
もFunction.prototype
を参照し、Objectオブジェクトと同様のチェーンを辿ります。FunctionオブジェクトのプロトタイプチェーンFunction.__proto__ === Function.prototype // true ↓ Function.prototype.__proto__ === Object.prototype; // true ↓ Object.prototype.__proto__ === null // truetoStringメソッドのややこしい話
Objectオブジェクトの持つメソッドのひとつに、
toString()
があります。
Object.prototype.toString() - JavaScript | MDN以下の例は同じ
toStringメソッド
を参照しているように見えますが、結果が異なります。toStringメソッドの比較Object.toString === Object.__proto__.toString // true Object.toString === Object.prototype.toString // false
理由を調べるため、
Object.toString
のプロトタイプチェーンを辿っていくと、
その参照先はFunction.prototype
を指していることが分かりました。Object.toStringのプロトタイプチェーンObject.toString // ObjectオブジェクトにはtoStringメソッドは存在しない ↓ // Object.__proto__を参照する Object.__proto__ === Function.prototype // true ↓ // Function.prototypeにtoStringメソッドがないか確認する。 Function.prototype.toString // Function.prototypeにはtoStringメソッドが存在するので、ObjectはtoStringメソッドを使える。 ↓ // Function.prototype.__proto__も見てみる。 Object.prototype.toString // 親(prototype)と子で同名のメソッドを持つ場合、親メソッドは子メソッドでオーバーライド(上書き)される。
Object.prototype
が持つtoStringメソッド
と、Function.prototype
が持つtoStringメソッド
ではメソッドの内容が異なります。
Objectオブジェクト
はその両者とも継承していますが、メソッドのオーバーライドにより、親(Object.prototype
)のメソッドが子(Function.prototype
)のメソッドにより上書かれています。よって持っている
toStringメソッド
の起源が異なることが、最初の例の真相です。toStringメソッドの書き換えObject.toString === Object.__proto__.toString // true ↓ // 書き換えると Function.prototype.toString === Function.prototype.toString // true Object.toString === Object.prototype.toString // false ↓ // 書き換えると Function.prototype.toString === Object.prototype.toString // falseObject.prototype.toString()とFunction.prototype.toString()は別のメソッドなんですね。
参考
図で理解するJavaScriptのプロトタイプチェーン - Qiita
なるほど。。__proto__とprototypeの違い | 武骨日記
継承とプロトタイプチェーン - JavaScript | MDN
Object - JavaScript | MDN
Object.prototype - JavaScript | MDN
Object.prototype.__proto__ - JavaScript | MDN
Object.prototype.toString() - JavaScript | MDN
Object.create() - JavaScript | MDN
Function - JavaScript | MDN
Function.prototype.toString() - JavaScript | MDN
例えば
Object.create(null)
のようなオブジェクトはプロトタイプを持たないので、Object.prototype
から継承を受けていない。 ↩
- 投稿日:2020-02-24T02:28:05+09:00
e-typing(タイピングゲーム)で自作の文章で練習できるようにする
はじめに
e-typingという有名なタイピングゲームがあります。
https://www.e-typing.ne.jp/これ自分で作った文章入力出来たら面白そうと思ってトライしてみました。
やったこと
- e-typingを解析して仕組みを知る
- 自分のサーバーに移植する
- 自分で作った例文を読み込めるようにする
- 実際にプレイしてみる
結果
実際に完成したもの。
もちろんデザインとかシステムとか全く同じなのでただのe-typingクローンです。
しかし、ちゃんと自作の例文を使ってタイピング練習できます。判明したこと
こちらで詳しく紹介していますが、e-typingの例文データは暗号化された状態でサーバーから送られてくることがわかりました。
それをJavaScriptでデコードした後に表示しているみたいです。例文の元データはこんなかんじ
s0=#0#mmdlfjrxeedlekeglkemumbamlevtmhrkmdvamzzgzevtmybmbdmamzzbidlktuzjbevumxbx#1#jcemumvrytdvtxdwwbfvavlsxmdmumwxawfvambuvxevtmyrxpdvtvzzpnemtmhbfyemuvwwcmfmambkitemalgfpwemavxrl#2#mqdbgnbldbfbgklvufebgqldmodbgnxeihebonbxexerskmutqdbgqlbwvdbsqxvqxebskmekeebsqmtqbfrokvzqnfbonlblvfbgnmwvpdrgnvwesebokxbnfdbsqmwnwfbsqlembfrgkrvmbebowguvudbgnbmdlfbgqlgdhfrsnlmj#3#811複合化キーみたいなものも含まれていて、とても面白い仕組みです。
さいごに
ほぼ丸パクリなので公開などは行わない予定です。
ただ、暗号化や解読の関数は紹介しているので興味があればチェックしてみてください。以上になります。
- 投稿日:2020-02-24T01:12:43+09:00
Automation Anywhere A2019でPython/JavaScript/VBScriptのインラインスクリプティングでプログラミングを行う
Automation Anywhere A2019では、外部スクリプトや外部プログラムを呼び出すことができます。スクリプト言語で対応をしているのは Python / Javascript / VBScript の3つで、これらは別のファイルとして用意しておくこともできますし、Botにインラインスクリプトとして埋め込んで実行することもできます。別のファイルにしておく場合は、ローカルで作成したファイルをControl Roomにあらかじめアップロードすることになります。いずれの場合もスクリプトはローカルPCの環境で実行されます。
インラインスクリプティングの方法
Python
- Botエディターで、アクションパレットから「Python script: Open」を探してドラッグ&ドロップします。
- 「Python script: Open」アイコンを選択してアクションの詳細を開きます。そこで「Manual input」を選択します。
- 「Enter script here」のテキストボックスに以下のコードを貼り付けて保存します。
Inlinescriptfrom tkinter import messagebox messagebox.showinfo('Python', 'Hello World')4.最後に「Python script: Execute script」と、「Python script: Close」を探して、フローの最後にドラッグ&ドロップし保存すれば完成です。
注: Pythonコードを実行するには、ローカルPCにPython実行環境の構築が必要です。詳しくは @zamaezaaa さんの記事『A2019 Community EditionでPythonコードを動かす』を参照してください。Python実行環境がない環境でBotを実行した場合、Botで何も実行されずに正常終了しますが、Bot Agentがフリーズして次のBotが実行できなくなり、再起動が必要になる場合があります。
参考情報
- 『Python スクリプト パッケージ』12 - 製品ドキュメント
Javascript
- Botエディターで、アクションパレットから「JavaScript: Open」を探してドラッグ&ドロップします。
- 「JavaScript: Open」アイコンを選択してアクションの詳細を開きます。そこで「Manual input」を選択します。
- 「Enter script here」のテキストボックスに以下のコードを貼り付けて保存します。
Inlinescriptfunction getRandom() { return Math.floor( Math.random()*10000 ); }4.「JavaScript: Run JavaScript」を探してフローの最後にドラッグ&ドロップし、アクションの詳細で、実行する関数名に
getRandom
、出力の格納先に$prompt-assignment$
変数を指定します。5.最後に「JavaScript: Close」を探してフローの最後にドラッグ&ドロップし、その後ろにMessageBoxアクションを挿入して
$prompt-assignment$
変数を表示するように設定して保存すれば完成です。注: JavaScript の場合は、
alert
、prompt
、writeln
のようなブラウザーが実装している画面出力や、WScript.Echo
、WScript.StdOut.WriteLine
のようなWSHオブジェクトを利用するとエラーになったため、値の処理だけJavaScriptで実行して、結果はAutomation AnywhereのMessageBoxで表示するようにしました。参考情報
- 『JavaScript パッケージ』12 - 製品ドキュメント
VBScript
- Botエディターで、アクションパレットから「VBScript: Open」を探してドラッグ&ドロップします。
- 「VBScript: Open」アイコンを選択してアクションの詳細を開きます。そこで「Manual input」を選択します。
- 「Enter script here」のテキストボックスに以下のコードを貼り付けて保存します。
InlinescriptMsgBox "Hello World"4.最後に「VBScript: Run function」と、「VBScript: Close」を探して、フローの最後にドラッグ&ドロップし保存すれば完成です。
参考情報
- 『VBScript パッケージ』12 - 製品ドキュメント
製品ドキュメントの内容は頻繁にアップデートされているので英語版のドキュメントを参照するのがお勧めです。 ↩
最新の英語ドキュメントを見ると、Pythonの場合は関数に渡せる引数は1つだけ(Boolean, dictionary, list, number, string)、JavaScript/VBScriptの場合はリスト型変数を渡すことで複数の引数を渡せる(Boolean, Datetime, number, string etc.)とあります。ただし、A2019.10 (ビルド2545)では、VBScriptではアクションの詳細の「Arguments to the function」にてリスト型変数以外にも文字列や数値も入力可能となっています。 ↩
- 投稿日:2020-02-24T00:06:48+09:00
新米フロントエンドエンジニア がお世話になってるサイトまとめ
はじめに
今月よりフロントエンドエンジニアとして働き始めたあくしおです。
業務で進めていく中で自分が普段参考にしていたり勉強で使用しているサイトを羅列していこうと思います。「こんなもん参考にしてるうちはダメだぞ!」って声もあるかもしれませんが許してください
HTML編
タグが非常に分かりやすくまとまっており(アルファベット順)画像の通り、そのタグが
inline要素
なのか
block要素
なのかなどが視覚的に非常に分かりやすくなっています。そのままタグをクリックするとそのタグについての説明が記載されています。
使い所やサンプルコードまで乗っているのでめちゃくちゃオススメのサイトです。
英語のサイトになるのですがChromeの拡張機能などで十分理解できるレベルに翻訳されるので英語アレルギーのある方もぜひ!その名のとおりHTML5のそれぞれの要素が入れ子にできる「子要素」、包含されることが可能な「親要素」の一覧のルールを視覚化して表示してくれます。
CSS編
はい。先ほどのサイトの姉妹サイトです
HTML ReferenceのCSSバージョンだと思っていただければ問題ありません。
ここに長々と説明書くより元のサイト飛んでもらえれば一番分かりやすいSass
はい。CSSのメタ言語(CSSプリプロセッサ)Sassですね
読み方はサスなんでしょうか、サースなんでしょうか、今だに分かっていません。いきなり脱線しましたがおすすめサイトはこちらになります。
SassMeister
このように入力したSassのコードをリアルタイムでCSSにコンパイルしてくれるツールです。 Sassが使える環境を構築しなくても、手軽にSassを試すことができます。
有名なので知っている方も多いかと思います。
即座にコンパイル結果が出力されるのでSassの扱いに慣れてないうちは特にいいのではと思います。JavaScript編
勉強サイトばかりです。
現代のJavaScriptチュートリアル
その名の通りチュートリアルです。
基本的なことから高度なトピックまで扱われているので個人的にイチオシサイトですしかしかなりのボリュームがあるので毎日少しずつやっていくのがいいと思います。
ちなみにオススメしている本人は終わってません。すいません二つ目はこちら
著書は有名なazuさんです。2016年よりAngular日本ユーザー会の代表を務めておられるようです (凄い...こちらもJavaScriptを学び始める初心者の方にオススメです!
海外のチュートリアルを訳した感じの違和感?のある感じではなく最初から日本人の方が書かれているので自然に読めます!終わりに
とりあえずパッと思いついたものだけですが羅列してみました。
他にもこんなサイトあるぞ、とかこのサイトもオススメや。みたいなのがあればコメントで教えてください!それではみなさまお元気で