- 投稿日:2019-11-12T23:03:50+09:00
VSCodeのスニペットでconsole.log()をちょっと便利に。
ある日の弊社
社長「お〜い、やめ太郎」
ワイ「なんでっか、社長?」
社長「今度作るショッピングサイトの件なんやけど」
社長「ドラッグ&ドロップ機能を実装することになったから」
社長「それをやめ太郎にお願いしたいんや」
社長「やめ太郎、やったことあるか?」ワイ「どどどドラッグ!?」
ワイ「あるわけないやないですか・・・!」
ワイ「そんなもの販売して万が一・・・」ハスケル子「やめ太郎さん、違いますよ」
ハスケル子「ドラッグ&ドロップでしょ」
ハスケル子「マウスで掴んで移動させるやつです」ワイ「ああ、そっちか」
社長「やめ太郎、できるか?」
ワイ「もちろんですわ」
ワイ「前職でもドラッグやってましたから、余裕ですわ」社長「よっしゃ、よろしく頼むわ!」
レッツJavaScript
ワイ「確かドラッグ&ドロップを実装するときは」
ワイ「マウスを動かしたときにマウスポインタの座標を取得できるようにして」
ワイ「その座標の数値を元に、DIV要素なり画像なりを移動させてやればええんや」ワイ「せやからまずは、こうやな」
// 「マウスが動くたびに実行したい関数」を作成。 const func = (e) => { // マウスの横座標・縦座標を取得。 const yoko = e.pageX; const tate = e.pageY; // それをコンソールに表示。 console.log(yoko); console.log(tate); }; // ユーザがマウスを動かすたびにfunc関数が実行されるように登録。 window.addEventListener('mousemove', func);ワイ「これでconsoleを見てみよか」
ワイ「ちゃんとマウスの座標を取得できとるかな」ワイ「おお、できとるできとる」
しかし、なんか分かりづらい
ワイ「どれが縦座標でどれが横座標の数値なのか分からんな・・・」
ワイ「アカン、この案件、ギブアップや・・・」ハスケル子「(もうギブアップ!?)」
ハスケル子「こ、こうすればどうですか?」console.log('yoko:', yoko); console.log('tate:', tate);ハスケル子「これでconsoleを見てみると」
ハスケル子「ほら」
ワイ「おお、見やすいな」
ワイ「変数名も一緒に出力するんやな」引数2つ渡せるんやね
ワイ「ていうか、
console.log()
って引数2つ渡せるんやね」ハスケル子「はい」
ハスケル子「というか何個でも渡せますよ」ワイ「そ、そうなんか・・・!」
ワイ「知らなんだわ」
ワイ「今まで・・・」console.log('yoko: ' + yoko);ワイ「↑こんな感じで文字列として連結して出力しとったわ」
ハスケル子「あるあるですね」
でも、ちょっと面倒くさい
ワイ「でも、ちょっと・・・」
javascript.jsonconsole.log('yoko:', yoko);ワイ「↑これをタイピングすんのが面倒やなぁ。。。」
ハスケル子「じゃあ、いいスニペットありますよ」
ワイ「巣にペット!?」
ワイ「せっかく飼ったペットを、巣に帰してしまうん・・・?」ハスケル子「まず、VSCodeの画面左下の歯車マークをクリックして」
ワイ「(完全スルーやん・・・)」
ハスケル子「ユーザースニペットを選択します」
ハスケル子「すると言語一覧が表示されるので」
ハスケル子「javascript.json
(JavaScript)
ってやつを選択します」
ハスケル子「あとはJSON形式で書けばいいだけなので」javascript.json{ "コンソールログのやつ": { "prefix": "cl", "body": "console.log('${1:xxx}:', ${1:xxx});", "description": "コンソールログをいい感じにするやつ" } }ハスケル子「↑こうですね」
ハスケル子「スニペットの名前は何でもいいので、"コンソールログのやつ"にして」
ハスケル子「description(説明書き)も適当に、"コンソールログをいい感じにするやつ"にしておきましょう」
ハスケル子「"prefix": "cl"
っていうのは」
cl
って打ったらワードを展開してくれや!ハスケル子「って意味です」
ハスケル子「で、その展開してほしいワードをbodyに書くんです」
ハスケル子「もし複数行のスニペットを作りたい場合はbodyを配列として書きます」javascript.json{ "コンソールログのやつ": { "prefix": "cl", "body": "console.log('${1:xxx}:', ${1:xxx});", "description": "コンソールログをいい感じにするやつ" }, "複数行のスニペット": { "prefix": "multi", "body": [ "1行目", "2行目", "3行目", ] } }ハスケル子「↑こうですね」
ハスケル子「descriptionは無くても動きます」それで、何が便利なん?
ワイ「なるほどな〜」
ワイ「ほんで、このスニペット何が便利なん?」ハスケル子「スニペットのbodyの中に、
${1:xxx}
と書くことで」
ハスケル子「スニペット展開後のデフォルトカーソル位置を指定してるんですけど」
ハスケル子「それをマルチカーソルにしてるところがちょっと便利ですよ」
ハスケル子「見ててくださいね・・・!」ハスケル子「まず、
yoko
という変数名を選択して、コピーします」
ハスケル子「そしてconsole.log()
したい場所に」
ハスケル「cl
とタイプしてTabキー
を押します」
ハスケル子「するとスニペットが展開されるうえに、2箇所のxxx
が選択されている状態になるので」
ハスケル子「あとは、さっきコピーしたyoko
をペーストするだけです」ワイ「おお・・・」
ワイ「ええやん・・・!」ワイ「何度も
console.log('yoko:', yoko);
って打つの」
ワイ「地味に嫌やもんな・・・」そして夕方
ワイ「ハスケル子ちゃん」
ワイ「今日も便利なものを教えてくれてありがとうやで」ハスケル子「どういたしまして」
ワイ「あー、でも」
ワイ「今日も新しいこといっぱい覚えたから、疲れたわぁ・・・」ハスケル子「楽しいけど疲れますよね」
ハスケル子「無理しすぎると体に毒だから、今日はもう上がったらいいんじゃないですか」ワイ「でも、まだ定時まで1時間以上あるし・・・」
ハスケル子「やめ太郎さん」
ハスケル子「今日8時間働くことと」
ハスケル子「この先何年も健康に働くこと」
ハスケル子「どっちが大事ですか・・・?」ワイ「ハ・・・ハスケル子ちゃん・・・」
ハスケル子「今日は上がってください」
ワイ「おおきにやで・・・ハスケル子ちゃん・・・!!!」
社長「(インターンなのに勝手に社員を帰らさんといて・・・)」
〜おしまい〜
- 投稿日:2019-11-12T23:03:50+09:00
マルチカーソルで、VSCodeのスニペットをもっと便利に。
ある日の弊社
社長「お〜い、やめ太郎」
ワイ「なんでっか、社長?」
社長「今度作るショッピングサイトの件なんやけど」
社長「ドラッグ&ドロップ機能を実装することになったから」
社長「それをやめ太郎にお願いしたいんや」
社長「やめ太郎、やったことあるか?」ワイ「どどどドラッグ!?」
ワイ「あるわけないやないですか・・・!」
ワイ「そんなもの販売して万が一・・・」ハスケル子「やめ太郎さん、違いますよ」
ハスケル子「ドラッグ&ドロップでしょ」
ハスケル子「マウスで掴んで移動させるやつです」ワイ「ああ、そっちか」
社長「やめ太郎、できるか?」
ワイ「もちろんですわ」
ワイ「前職でもドラッグやってましたから、余裕ですわ」社長「よっしゃ、よろしく頼むわ!」
レッツJavaScript
ワイ「確かドラッグ&ドロップを実装するときは」
ワイ「マウスを動かしたときにマウスポインタの座標を取得できるようにして」
ワイ「その座標の数値を元に、DIV要素なり画像なりを移動させてやればええんや」ワイ「せやからまずは、こうやな」
// 「マウスが動くたびに実行したい関数」を作成。 const func = (e) => { // マウスの横座標・縦座標を取得。 const yoko = e.pageX; const tate = e.pageY; // それをコンソールに表示。 console.log(yoko); console.log(tate); }; // ユーザがマウスを動かすたびにfunc関数が実行されるように登録。 window.addEventListener('mousemove', func);ワイ「これでconsoleを見てみよか」
ワイ「ちゃんとマウスの座標を取得できとるかな」ワイ「おお、できとるできとる」
しかし、なんか分かりづらい
ワイ「どれが縦座標でどれが横座標の数値なのか分からんな・・・」
ワイ「アカン、この案件、ギブアップや・・・」ハスケル子「(もうギブアップ!?)」
ハスケル子「こ、こうすればどうですか?」console.log('yoko:', yoko); console.log('tate:', tate);ハスケル子「これでconsoleを見てみると」
ハスケル子「ほら」
ワイ「おお、見やすいな」
ワイ「変数名も一緒に出力するんやな」引数2つ渡せるんやね
ワイ「ていうか、
console.log()
って引数2つ渡せるんやね」ハスケル子「はい」
ハスケル子「というか何個でも渡せますよ」ワイ「そ、そうなんか・・・!」
ワイ「知らなんだわ」
ワイ「今まで・・・」console.log('yoko: ' + yoko);ワイ「↑こんな感じで文字列として連結して出力しとったわ」
ハスケル子「あるあるですね」
でも、ちょっと面倒くさい
ワイ「でも、ちょっと・・・」
javascript.jsonconsole.log('yoko:', yoko);ワイ「↑これをタイピングすんのが面倒やなぁ。。。」
ハスケル子「じゃあ、いいスニペットありますよ」
ワイ「巣にペット!?」
ワイ「せっかく飼ったペットを、巣に帰してしまうん・・・?」ハスケル子「まず、VSCodeの画面左下の歯車マークをクリックして」
ワイ「(完全スルーやん・・・)」
ハスケル子「ユーザースニペットを選択します」
ハスケル子「すると言語一覧が表示されるので」
ハスケル子「javascript.json
(JavaScript)
ってやつを選択します」
ハスケル子「あとはJSON形式で書けばいいだけなので」javascript.json{ "コンソールログのやつ": { "prefix": "cl", "body": "console.log('${1:xxx}:', ${1:xxx});", "description": "コンソールログをいい感じにするやつ" } }ハスケル子「↑こうですね」
ハスケル子「スニペットの名前は何でもいいので、"コンソールログのやつ"にして」
ハスケル子「description(説明書き)も適当に、"コンソールログをいい感じにするやつ"にしておきましょう」
ハスケル子「"prefix": "cl"
っていうのは」
cl
って打ったらワードを展開してくれや!ハスケル子「って意味です」
ハスケル子「で、その展開してほしいワードをbodyに書くんです」
ハスケル子「もし複数行のスニペットを作りたい場合はbodyを配列として書きます」javascript.json{ "コンソールログのやつ": { "prefix": "cl", "body": "console.log('${1:xxx}:', ${1:xxx});", "description": "コンソールログをいい感じにするやつ" }, "複数行のスニペット": { "prefix": "multi", "body": [ "1行目", "2行目", "3行目", ] } }ハスケル子「↑こうですね」
ハスケル子「descriptionは無くても動きます」それで、何が便利なん?
ワイ「なるほどな〜」
ワイ「ほんで、このスニペット何が便利なん?」ハスケル子「スニペットのbodyの中に、
${1:xxx}
と書くことで」
ハスケル子「スニペット展開後のデフォルトカーソル位置を指定してるんですけど」
ハスケル子「それをマルチカーソルにしてるところがちょっと便利ですよ」
ハスケル子「見ててくださいね・・・!」ハスケル子「まず、
yoko
という変数名を選択して、コピーします」
ハスケル子「そしてconsole.log()
したい場所に」
ハスケル「cl
とタイプしてTabキー
を押します」
ハスケル子「するとスニペットが展開されるうえに、2箇所のxxx
が選択されている状態になるので」
ハスケル子「あとは、さっきコピーしたyoko
をペーストするだけです」ワイ「おお・・・」
ワイ「ええやん・・・!」ワイ「何度も
console.log('yoko:', yoko);
って打つの」
ワイ「地味に嫌やもんな・・・」そして夕方
ワイ「ハスケル子ちゃん」
ワイ「今日も便利なものを教えてくれてありがとうやで」ハスケル子「どういたしまして」
ワイ「あー、でも」
ワイ「今日も新しいこといっぱい覚えたから、疲れたわぁ・・・」ハスケル子「楽しいけど疲れますよね」
ハスケル子「無理しすぎると体に毒だから、今日はもう上がったらいいんじゃないですか」ワイ「でも、まだ定時まで1時間以上あるし・・・」
ハスケル子「やめ太郎さん」
ハスケル子「今日8時間働くことと」
ハスケル子「この先何年も健康に働くこと」
ハスケル子「どっちが大事ですか・・・?」ワイ「ハ・・・ハスケル子ちゃん・・・」
ハスケル子「今日は上がってください」
ワイ「おおきにやで・・・ハスケル子ちゃん・・・!!!」
社長「(インターンなのに勝手に社員を帰らさんといて・・・)」
〜おしまい〜
- 投稿日:2019-11-12T21:57:46+09:00
AtCoder供養
JSのES6でAtCoderをやろうとしたら、「letもconstも使えません 分割代入でなんかエラー出てるぞ」と言われて萎えたのでコードを供養します
はじめてのBFS...
迷路をツリー構造に格納しーの、キューを使って探索してます
やり方がまったくわかりませんでしたconst maze = '######## #.#....# #.###..# #......# ########'.split(' ') const [r, c] = [maze.length, maze[0].length] const [sr, sc] = [1,1] const [gr, gc] = [1,3] // SAMPLE DATA STRUCTURE // const root = { // r: 1, // c: 1, // children: [ // { // r: 2, // c: 1, // children: [ // ... // ] // } // ] // } const printNode = n => { console.log('['+n.r+','+n.c+']' + 'depth: ' + n.d) } var visited = [[1,1]]; // root node is already visited const addChild = p => { const range = [[-1, 0], [1, 0], [0, -1], [0, 1]] for (d of range) { const [dr, dc] = d let child = {} child.r = p.r + dr child.c = p.c + dc child.d = p.d + 1 child.children = [] if (child.r >= 0 && child.r < r && child.c >= 0 && child.c < c && maze[child.r][child.c] == '.' && !visited.some(e => e[0] == child.r && e[1] == child.c)) { visited.push([child.r, child.c]) p.children.push(child) addChild(child) } } } const start = { r: 1, c: 1, d: 0, children: [] } addChild(start) var res; const q = [start] while (q.length) { const row = q.shift() if (row.children) { row.children.forEach(child => { if (child.r == gr && child.c == gc) { res = child.d return } q.push(child) }) } } console.log(res)
- 投稿日:2019-11-12T20:58:40+09:00
JavaScriptのDateインスタンス生成用ラッパー
JavaScriptで特定の日時のDateオブジェクトのインスタンスを生成する方法には、いくつかあって、以下のように文字列を入れたり、年・月・日...を数値として渡す方法などがある。
new Date('December 17, 1995 03:24:00'); new Date(1995, 11, 17, 3, 24, 0);しかしながら、文字列を渡してインスタンスを生成する場合罠があって、例えば以下のようなフォーマットの場合、ChromeやFirefoxなら正常にインスタンス生成できても、Safariだとエラーになるということがある。
new Date('2019-11-08 11:25') // Firefox → Date Fri Nov 08 2019 11:25:00 GMT+0900 (日本標準時) // Chrome → Fri Nov 08 2019 11:25:00 GMT+0900 (日本標準時) // Safari → Invalid Date以上から、文字列から直接Dateオブジェクトのインスタンスを生成するのは危険なので、文字列をパースして、
new Date(yyyy,MM,dd,HH,mm,ss,SSS)
の形式で生成してくれるラッパーを作成した。function constructDate (datetimeString, format){ function extract(datetimeString, expression){ var start = format.search(expression); if(start < 0){ return ''; } var extracted = datetimeString.substr(start, expression.length); if(expression !== 'MM'){ return datetimeString.substr(start, expression.length); } else{ var month = Number(extracted) - 1; return ('0' + month).slice(-2); } } var dt ={ yyyy: extract(datetimeString, 'yyyy'), MM: extract(datetimeString, 'MM'), dd: extract(datetimeString, 'dd'), HH: extract(datetimeString, 'HH'), mm: extract(datetimeString, 'mm'), ss: extract(datetimeString, 'ss'), SSS: extract(datetimeString, 'SSS') }; var constructCommand = 'new Date('; for(var key in dt){ if(dt[key] !== ''){ constructCommand += dt[key] + ','; } } constructCommand +=')'; return eval(constructCommand); }; // 使用例 constructDate('2019-11-26 11:25:39', "yyyy-MM-dd HH:mm:ss") // Date Tue Nov 26 2019 11:25:00 GMT+0900 (日本標準時) constructDate('2019/11/26 11:25', "yyyy-MM-dd HH:mm") // Date Tue Nov 26 2019 11:25:39 GMT+0900 (日本標準時) constructDate('20191126', 'yyyyMMdd') // Date Tue Nov 26 2019 00:00:00 GMT+0900 (日本標準時)おそらくN番煎じかつ、適切なライブラリを使えば解決できそうな気もするけど、参考になれば。
- 投稿日:2019-11-12T20:03:43+09:00
Promiseのresolveとrejectの両方を呼び出したらどうなってしまうのか?
Promise
は次のサンプルコードように、失敗したらreject
を、成功したらresolve
を呼び出すような書き方をする。new Promise((resolve, reject) => { // ... if (err) { reject(err) } else { resolve() } })このサンプルコードは、正常系と異常系でif分岐されており、
resolve
とreject
が両方呼ばれる可能性はない。もしも、
resolve
とreject
の両方を呼び出したらどうなるのだろうか?new Promise((resolve, reject) => { reject() resolve() })普通はこんなことはしないだろうが、
Promise
の仕様を知っておくのは良さそうだ。実験1:
resolve
→reject
の順で呼んでみる実験してみよう。まずは、
resolve
→reject
の順で呼び出してみよう。new Promise((resolve, reject) => { resolve() reject() }).then(value => console.log('success')) .catch(reason => console.log('failure')) //=> successこの実行結果は、「success」になり、
resolve
が尊重されたかたちになった。実験2:
reject
→resolve
の順で呼んでみる今度は逆に、
reject
のほうを先に呼び出してみることにする。new Promise((resolve, reject) => { reject() resolve() }).then(value => console.log('success')) .catch(reason => console.log('failure')) //=> failure今度の結果は「failure」になった。
reject
のほうが採用されたことが分かる。結論: 先に呼ばれたほうが採用される
「Promiseの
resolve
とreject
の両方を呼び出したらどうなってしまうのか?」の疑問への答えとしては、「先に呼ばれたほうが採用される」ということになりそうだ。余談: 先勝ちと言っても、処理が中断されるわけではない
ちなみに、
resolve
とreject
どちらかが採用されるとは言え、Promise
に渡したexecuterの処理が中断されるわけではない。例えば、下記のコードでは、①でresolve
が採用されるが、そのあとの②③の行も実行される。new Promise((resolve, reject) => { resolve() // ① console.log('after resolve') // ② reject() // ③ }).then(value => console.log('success'))Learn more
- 投稿日:2019-11-12T19:29:35+09:00
canvas要素の描画を任意のタイミングで切り替えたい
はじめに
canvas要素を最前面に配置したかったが、アプリ起動時からcanvasを展開すると裏の要素の操作ができなくなってしまいます。
なお描画するエフェクト(紙吹雪)はjsdo.itにあったものを利用しました。
(現在は閉鎖されてしまっているようです)環境
- Mac OS X EL Capitan 10.14.5
解決策
紙吹雪を描画する関数内では
setInterval
関数が用いられおり、それによって継続的な動作が行われていました。以下が実際のコードです。
setInterval(draw, 20);
20msごとにdraw関数を呼び出す処理が行われています。この処理を止めるためには、
setInterval
に対応するclearInterval
を用いました。描画を停止することでcanvasを消し、下の要素への操作が可能になりました。
実装例
今回はアプリ内で任意の箇所をクリックした時に描画を停止できるよう、外部の関数として停止処理を実装しました。
var carryout; function celebrate() { function draw{ /* 描画処理は省略 */ } //20msごとに描画繰り返し carryout = setInterval(draw, 20); } //呼び出された時にIntervalを破棄 function stopDraw() { clearInterval(carryout); }この関数をクリック時に呼び出すことで停止処理を実現することができた。
デモ
以下のように任意のタイミングでcanvasのオンオフを切り替えることができます。
終わりに
他にも実装方法がありそうだと思ったが、Interval関数が使われている場合これが一番簡単に実装できそう。
止まり方が少し不自然かも?
- 投稿日:2019-11-12T19:24:24+09:00
カリー化関数と部分適用の使いどころを考える
複数の引数を取る関数が、以下のように特定の変数だけ変わる偏った呼ばれ方をすることが想定される場合、
someFunc(hoge,piyo,1); someFunc(hoge,piyo,2); someFunc(hoge,piyo,3); someFunc(hoge,piyo,4); someFunc(hoge,piyo,5);こんな感じにカリー化した関数があると、
部分適用した関数を利用してすっきりと書ける。const curriedSomeFunc = (hoge,piyo) => (num) => { someFunc(hoge,piyo,num); } const partiallyApplied = curriedSomeFunc(hoge,piyo); partiallyApplied(1); partiallyApplied(2); partiallyApplied(3); partiallyApplied(4); partiallyApplied(5);普通は通常の関数のみを外部に公開するけれども、
ユースケースが明らかに偏りそうな場合はカリー化関数も併せて出してあげると親切かもしれない。
- 投稿日:2019-11-12T18:54:41+09:00
はじめてのGAS
ExcelからGoogle Spreadsheetに移行した。
これからは、GAS(Google Apps Script)による自動化をバリバリやってみようと思う。
ここでは、初めてのGASの作成の模様を、自分用メモとして書く。目標
これを
- 1行目の背景:濃い青
- 1行目の文字色:黄色
- 以下偶数行目の背景:薄い青
- 以下奇数行目の背景:中くらいの青
のように設定を与えると、ずーっとシマシマに塗っていくというものだ。
こうなって欲しい。
では行ってみよう。以下はおいらがやりやすいやり方を取っているだけで、この通りやらないといけないわけじゃない。
もっと頭がいい人はエッセンスだけ汲み取ってくれてもいいし、改善点を指摘してくださってもいい。
また、盲目的に以下の手順に従っても、GASのバージョンが変わった、おいらの環境とおたくの環境が違う、おいらの記述に不備があったなどの理由で、うまく動かないかもしれない。
かなりレアケースで、何らかの不利益がもたらされるかもしれないが、そこは自己責任でお願いします。。0. 作業環境を用意する
おいらの環境は以下の通り。
1) Windows 10 Pro 64 bit
2) Google Chrome Canary(会社用は無印Chrome、自分用はCanaryで分けた)
3) 自分用のGoogleアカウント1. スプレッドシートを準備する
※この項目にGASの知識は1ミリも出てきません。
1) Googleホームページで■が3×3個並んでいるアプリメニューをクリックし、ドライブを選択する
2) マイドライブ画面で、+新規をクリックする
3) スプレッドシートをクリックする=>無題のスプレッドシートが出来る(以下シート画面と呼ぶことがある)
4) 「無題のスプレッドシート」というタイトルをダブルクリックして「GAS Study」などと変名する
5) アドレスバーの★ボタンを押してブックマークバーに「GAS Study」としてブックマークする
6) シート1に色付き前のテストデータを作る。
7) シート1のシート名を右クリックしてコピーを作成を選ぶ作業を2回行う
(テストデータを取っておくため。1回だと間違えて壊すこともあるから)
8) この状態で自分に取っての準備完了。
2. Hello Worldとダイアログ表示する
1) ツール→スクリプトエディタを選択する(以下スクリプト画面と呼ぶことがある)
=>無題のプロジェクトというプロジェクト、コード.gsというファイル、myFunction()という関数が自動で出来ている
2) myFunctionをhelloDialogと変更する
3) {}の間に以下のコードを書く。Browser.msgBox("Hello World");=>未保存の編集がある場合はコード.gsタブ名の左横にダーティサイン(赤い*)が表示されている。
4) Ctrl+Sで保存しようとしたら、プロジェクト名の命名を求められたので、GAS Studyとした。
=>保存が起こり、ダーティサインが消え、プロジェクト名が変わった。
5) シートのタブとスクリプトのタブを分離し、左右に並べた。
※実際に現象が起こるシート画面とスクリプト画面を分けたほうが見やすいため
6) エディタ画面の虫アイコン(デバッガー)の右横で、helloDialogという現在唯一の関数が表示されていることを確認する
7) 虫の左横のプレイボタン(▶)をクリックする=>初回だけAuthorization requiredというダイアログが表示される
8) 「許可を確認」をクリックする
9) 「アカウントの選択」が表示されるので、自分のアカウントを選択する
10) 「▲このアプリは確認されていません」と警告されるので「詳細」をクリックする
11) 下に表示される「GAS Study(安全でないページ)に移動」をクリックする
12) 「アカウントへのアクセスをリクエストしています」と表示される。下の方の「許可」をクリックする
=>エディタ画面に「Running function helloDialog...」と表示され、シート画面に「Hello World」と表示される。やったー
13) シート画面でOKをクリックしてダイアログを閉じる3. 現在のセルの背景色を塗る
いよいよ実のあるプログラミングを行う。
やりたいことをググりまくって、いろんなページからコピペしまくって、試行錯誤でコードを書いた。
参考のページはリンクを貼るのがスジだが、あまりにもいろんなページを渉猟しまくって訳が分からなくなってしまったし、(あくまで好みの問題で)しょうじき紹介するには微妙なページもあるので割愛。
スミマセン。ま、最終的には公式のリファレンスを参考にすることになるだろう。
https://developers.google.com/apps-script/reference/spreadsheetまず、現状カーソルがあるセルを青く塗ってみる。
1) エディタ画面に以下のように入力する。
function paintHereBlue () { var sheet = SpreadsheetApp.getActiveSheet(); var cell = sheet.getActiveCell(); cell.setBackground("blue"); }2) Ctrl+Sでダーティサインを消す
3) 虫の右の関数名をpaintHereBlueに変える
4) シート画面で塗りたいセルをクリックする(左ではA1をクリックした)
5) エディタ画面でプレイボタンをクリックする
=>セルが青くなる。やったー
プログラムの解説を試みる
ここでプログラムの解説を試みる。
おいらGASもJavaScriptもシロートだから真に受けないでください。function paintHereBlue () { var sheet = SpreadsheetApp.getActiveSheet(); var cell = sheet.getActiveCell(); cell.setBackground("blue"); }paintHereBlueは関数名だ。
()には引数が書けるけど上では何も渡していない。varは変数を宣言する。
ここではアクティブシートをsheetという変数に入れることにした。
=は代入を行う。SpreadsheetAppは組み込みのクラス名だ。
人のプログラムを見ていて分からないものが出てきたら、公式リファレンスで虫眼鏡(?)アイコンのついた検索ボックスで検索しておくと良い。
https://developers.google.com/apps-script/reference/spreadsheet/spreadsheet-appgetActiveSheet()はSpreadsheetAppクラスのアクティブシートを得るメソッドだ。
https://developers.google.com/apps-script/reference/spreadsheet/spreadsheet-app#getactivesheetsetBackground()はrangeオブジェクトで背景色を塗るメソッドだ。
https://developers.google.com/apps-script/reference/spreadsheet/range#setBackgrounds(String)色は"red"、"green"などのフレンドリーな名前で指定することも、#ffffff(真っ白)のようなRGB値で指定することも出来る。
なお、varキーワードと;の間に、複数の変数定義と代入を入れることも出来る。
その場合、各定義の末尾には,を書く。
上のコードはこうも書けた。function paintHereBlue () { var sheet = SpreadsheetApp.getActiveSheet(), cell = sheet.getActiveCell(); cell.setBackground("blue"); }どっちがいいんだろ。
スクリプトエディタのエラー
なお、スクリプトエディタはなかなかインテリジェントで、行頭でタブを押すと正しくインデントされるし、ユーザー変数は最初は鮮やかな青で2回目からはくすんだ青など、工夫をこらして表示されるので、間違いは少ない。
文法レベルで間違ったコードを保存しようとすると、怒られて保存が出来ない。
下は;と間違えて:と書いて保存しようとしたところ。
正しく書き直してCtrl+Sを押すとエラーが消える。4. 現在のセルの文字色を変える
同様に文字色を変えるのはsetなんとかじゃないだろうかと思ってrangeオブジェクトのメソッドを見回していたら、setFontColorだった。
function paintHereBlueAndYellow () { var fontColor = "yellow", background = "blue", sheet = SpreadsheetApp.getActiveSheet(), cell = sheet.getActiveCell(); cell.setBackground(fontColor); cell.setFontColor(background); }2度目以降のテストを行う場合、シート画面でCtrl+Zを押すと、Undoされて、元通りの白バックに黒文字に変わる。
エディタ画面でプレイボタンがグレイになる場合は、虫の右が「関数を選択」となっているので、実行したい関数(ここではpaintHereBlueAndYellow)に変えてやる。
実行。■
スクショを取り忘れたので割愛するが、できでた。
なお、上のプログラムでは見やすさのために色の設定を行うfontColorとbackgroundという変数を用意して、先頭に出してみた。スクリプトエディタの補完
fontColorなどという長いつづりをいちいち打ち込んだり、コピペするのは大儀である。
スクリプトエディタでは、fonとか途中まで書いてAlt+/を押すと、Emacsライクな動的補完が起こり、fontColorと書いてくれる。
便利!5. 背景色を知りたい
これまではblue、yellowという色で察しがついたけど、他の色は英語で何ていうんだろう。
リファレンスの検索ボックスで「blue yellow white black red」と検索してみると、偉いものでEnum Colorという項目に当たった。https://developers.google.com/apps-script/reference/calendar/color
ううーん、でもなんか足りないなぁー。
もっと淡い色に塗りたい。
そこで、まずセルを塗ってから現在のセルの背景色を知るマクロを作る。function showBackgroundHere () { var sheet = SpreadsheetApp.getActiveSheet(); var cell = sheet.getActiveCell(); Browser.msgBox(cell.getBackground()); }明細A1をちょっと薄い色にして選択し、上の関数を走らせた。
またスクショをとりはぐれたが、「#cfe2f3」だと分かった。
同様に明細A2を少し濃い目にして選択し、「#9fc5e8」だと分かった。6. 最終セルを知りたい
最終セル(データが入っている行数と列数の最大値)を知りたい。
ところで、完全な閑話だが、日本語のエクセル(および行列演算)用語で横の並び(row)を行という。
縦の並び(column)を列という。
これは行の右のテみたいなところに注目して、横に線が走ってるから行と言う。
同様に列の右のリみたいなところに注目して、縦に線が走ってるから列と言う。
そうやって覚えればいいと高校の先生に習った。
そんなことはどうでもいい。function showLastCell () { var sheet = SpreadsheetApp.getActiveSheet(); var lastrow = sheet.getLastRow(); var lastcolumn = sheet.getLastColumn(); Browser.msgBox("行" + lastrow + "列" + lastcolumn); }7. セルの範囲を塗りたい
これまでは単一のセルを塗っていたが、範囲、たとえば4行1列から、4行3列までの3セルを塗りたいときはどうするか。
試行錯誤は省略するが、引数4つのgetRange()を使う。function paintRowPale () { var background = "#cfe2f3", row = 4, column = 3, sheet = SpreadsheetApp.getActiveSheet(), range = sheet.getRange(row, 1, 1, column); cell.setBackground(fontColor); }8. 組み立てる
さあ、さすがに材料が揃っただろう。
- 1行目の背景:濃い青
- 1行目の文字色:黄色
- 以下偶数行目の背景:薄い青
- 以下奇数行目の背景:中くらいの青
のように設定を与えると、ずーっとシマシマに塗っていくスクリプトを書く。
function blueStripe () { var background_header = "blue", fontcolor_header = "yellow", background_even = "#cfe2f3", background_odd = "#9fc5e8", sheet = SpreadsheetApp.getActiveSheet(), lastrow = sheet.getLastRow(), lastcolumn = sheet.getLastColumn(); sheet.getRange(1, 1, 1, lastcolumn).setBackground(background_header); sheet.getRange(1, 1, 1, lastcolumn).setFontColor(fontcolor_header); for (var row = 2; row <= lastrow; ++row) { var background; if (row % 2 == 0) { background = background_even; } else { background = background_odd; } sheet.getRange(row, 1, 1, lastcolumn).setBackground(background); } }9. ピンクバージョンを作る
- 1行目の背景:濃い赤
- 1行目の文字色:白
- 以下偶数行目の背景:薄いピンク
- 以下奇数行目の背景:中くらいのピンク
のように設定を与えると、ずーっとシマシマに塗っていくスクリプトを書く。
まず、blueStripeを関数化する。
なお、中間変数rangeを省いて、メソッドを数珠つなぎにしてみた。function colorStripe (background_header, fontcolor_header, background_even, background_odd) { var sheet = SpreadsheetApp.getActiveSheet(), lastrow = sheet.getLastRow(), lastcolumn = sheet.getLastColumn(); sheet.getRange(1, 1, 1, lastcolumn).setBackground(background_header); sheet.getRange(1, 1, 1, lastcolumn).setFontColor(fontcolor_header); for (var row = 2; row <= lastrow; ++row) { var background; if (row % 2 == 0) { background = background_even; } else { background = background_odd; } sheet.getRange(row, 1, 1, lastcolumn).setBackground(background); } }次に、それを呼び出す関数を作る。
function pinkStripe () { var background_header = "red", fontcolor_header = "white", background_even = "#ead1ac", background_odd = "#d5a6bd"; colorStripe(background_header, fontcolor_header, background_even, background_odd); }一時変数のbackground_headerとか要らなくて、いきなりリテラル引数でcolorStripe()を呼べばいいような気がするけど、使い方リファレンスを兼ねているので分かりやすい変数名つきで書いてもいい気がする。
実行。
でけた。
色は事前に適当なセルを塗って、showBackgroundHereで調べたけど、塗ってみるとちょっと肌色っぽくてキショイ感じがする。10. メニューから実行する
以下のコードを貼り付ければオッケー。
function onOpen() { var ui = SpreadsheetApp.getUi(); var menu = ui.createMenu('GAS Study'); menu.addItem('青のシマシマに塗る', 'blueStripe'); menu.addItem('ピンクのシマシマに塗る', 'pinkStripe'); menu.addToUi(); }addItem()の第1引数は表示されるメニューアイテム、第2引数は呼び出される関数だ。
onOpen()はスプレッドシートを開いた瞬間に実行される関数だ。(※スクショではStudyをSrudyって間違えてるけど許してくれ。。)
X. その他いろいろ
上記では紹介できなかったことを書く。
- Browser.msgBox()の代わりにLogger.log()を使うと表示→ログで出てくる実行ログにメッセージが出てくる
- プログラムの実行速度を測るためには、開始直後と終了直前にLogger.log()でメッセージを出すと、タイムスタンプが出るので大体分かる
- ループで時間が掛かっている場合、ある条件で周回を飛ばすといいことがある。continueを使う
- range.getBackground(background)は16進を文字列で返すので、"white"とかと比較してもうまくいかない。"#ffffff"と比較する。"#FFFFFF"もダメ
- encodeURI()という関数を使うと文字列をURLエンコード出来る
- メニューは階層化できる
Z. 今後の課題
- claspというのを使うとローカル(Emacs)で開発が出来る
- フォームと連動させるとウェブリケーションが出来る
- 現状自分ひとりで使っているが、人に使わせるにはデプロイということを行う
- Webスクレイピングも出来る
★★★
GASはスプレッドシートを使えばデータ構造も出来るし、UIもどきも出来るし、JavaScriptでモダンっていうの?に書けていい感じだ。
(この項おわり)
- 投稿日:2019-11-12T18:40:17+09:00
もうJavaScriptの配列操作で悩みたくない人生を生きるために
JavaScriptで配列を操作する時、覚える文法が多くて嫌になりませんか?(だよねーまじ面倒だわーという声が聞こえる・・・)
今日は1日かけて配列を復習したので、備忘録を兼ねてまとめていきます!!配列とは
let arr1 = [1,2,3,4,5]; let arr2 = [ {name:"takashi",age:12}, {name:"kiyoshi",age:22}, ]; console.log(ar1[3]); //4 console.log(ar2[0]); //{name:"takashi",age:12}簡単にいうと、変数の塊のことを指す。
私レベルの雑魚エンジニアになると、[ ]で囲むところを、{ }で囲んで、ずっと間違いに気がつかなかったりする・・・。空の配列を作る
let arr = new Array(); console.log(arr); //[]空の配列に同要素を詰め込んでみる
let arr = new Array(5).fill("hoge"); console.log(arr); //[ 'hoge', 'hoge', 'hoge', 'hoge', 'hoge' ] //new Array内で新しく作る要素の数を指定。fillはそれらを全て"hoge"に変更するおまじない。追加したり減らしたり
let arr = [1, 2, 3, 4, 5]; arr.push(6); //一番後ろに6を追加 arr.pop(); //一番末尾を切り出し console.log(arr); //[ 1, 2, 3, 4, 5 ] arr.unshift(0); //一番前に0を追加 arr.shift(); //一番先頭を切り出し console.log(arr); //[ 1, 2, 3, 4, 5 ]配列を合体する
let arr = [1, 2, 3, 4, 5]; let arr2 = arr.concat(6, 7); console.log(arr2); //[ 1, 2, 3, 4, 5, 6, 7 ]順番を変える
let arr = [1, 2, 3, 4, 5]; arr.reverse(); console.log(arr); //[ 5, 4, 3, 2, 1 ] arr.sort(); console.log(arr); //[ 1, 2, 3, 4, 5 ]要素の切り取り
let arr = [1, 2, 3, 4, 5]; let arr2 = arr.slice(3); console.log(arr2); //[ 4,5] 前から選んだ番号を含む以降を切り取り let arr3 = [1, 2, 3, 4, 5]; let arr4 = arr3.slice(-2); console.log(arr4); //[ 4,5 ]後ろから数えた番号を含む以降を切り取り要素の削除や追加
let arr = [1, 2, 3, 4, 5]; let arr2 = arr.splice(1, 4, 11, 111, 111); //2から4つ削除 console.log(arr); //[ 1, 11, 111, 111 ] //第三引数以降の数が削除された後に代入 console.log(arr2); //[ 2, 3, 4, 5 ] //切り取った値はこちらにspliceは第一引数に変更を開始する場所、第二引数に削除する要素の個数、第三引数より後に追加する要素を指定する。
要素を見つける
let arr = [1, 2, 3, 4, 5, 4, 3, 2, 1]; console.log(arr.indexOf(1)); //0 console.log(arr.lastIndexOf(1)); //8 console.log(arr.lastIndexOf(10)); //-1indexOfは前からカウント、lastIndexOfは後ろからカウント。存在しない場合は、-1が返ってくる。
要素の変換
let arr = [1, 2, 3, 4, 5]; let arr2 = arr.map(e => e * 2); console.log(arr2); //[ 2, 4, 6, 8, 10 ]2倍するところは、アロー関数使ってます。
要素のフィルタリング
let arr = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]; let arr2 = arr.filter(e => e < 50); console.log(arr2); //[ 10, 20, 30, 40 ] 50より小さい数だけを抜き出し。ここでもアロー関数使ってます。
配列全体を操作
let arr = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]; let arr2 = arr.reduce((a, e) => (a += e), 0); console.log(arr2); //550今回は第一引数(初期値を末尾で指定、今回は0)にarrの要素が代入されたxを全て足していく処理。
配列についてまとめてみました!
今以上に使いこなして、強強エンジニアを目指していきたいと思います!!
- 投稿日:2019-11-12T17:33:06+09:00
[Firebase]Storage上のファイル名一覧を表示する
前提条件・環境
- Firebase を JavaScript プロジェクトに追加するで、SDKの追加と初期化が完了している
- Storageのデフォルトパケット作成済
- ファイルアップロード済
- バケットURL指定済
- SDKのインポートはCDNを使用
手順
1. ルールの書き換え
ListAPIの使用にはFirebase Storage Rules バージョン2が必要なため、Storageにルールを加えます。デフォルトではFirebaseにログインしないと読み書きできないので、必要に応じて書き換えます。今回はテスト用に特に制限を設けません。
rules_version = '2'; service firebase.storage { match /b/{bucket}/o { match /{allPaths=**} { allow read; } } }2. SDKインポート
<script src="https://www.gstatic.com/firebasejs/6.2.0/firebase-storage.js">※上記SDKを追加しないと下記エラーになります。
"firebase.storage is not a function"3. 参照の取得
var storage = firebase.storage(); var storageRef = storage.ref();4. リスト表示
listAPIを使ってファイル参照一覧を取得します。
storageRef.listAll().then(function(result) { result.items.forEach(function(ref) { // 処理 }); }).catch(function(error) { console.error(error); });デモコード
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>TEST</title> <!-- Firebase App (the core Firebase SDK) is always required and must be listed first --> <script src="https://www.gstatic.com/firebasejs/6.2.0/firebase-app.js"></script> <!-- Add Firebase products that you want to use --> <script src="https://www.gstatic.com/firebasejs/6.2.0/firebase-auth.js"></script> <script src="https://www.gstatic.com/firebasejs/6.2.0/firebase-storage.js"></script> </head> <body> <p>Storageファイル一覧</p> <div id="table"></div> <script> var firebaseConfig = { ===== 省略 ======= ================= }; // Initialize Firebase firebase.initializeApp(firebaseConfig); // Get a reference to the storage service, which is used to create references in your storage bucket var storage = firebase.storage(); // Create a storage reference from our storage service var storageRef = storage.ref(); document.addEventListener('DOMContentLoaded', function() { var table = document.createElement( 'table' ); var tableBody = document.createElement("tbody"); storageRef.listAll().then(function(result) { result.items.forEach(function(ref) { var row = document.createElement("tr"); var cell = document.createElement("td"); cell.appendChild(document.createTextNode(ref.name)); row.appendChild(cell); tableBody.appendChild(row); table.appendChild(tableBody); }); document.getElementById("table").appendChild(table); }).catch(function(error) { console.error(error); }) }); </script> </body> </html>結果
参考サイト
ウェブでファイルのリストを取得する
How to get a list of all files in Cloud Storage in a Firebase app?
- 投稿日:2019-11-12T17:10:32+09:00
JavaScriptで関数型指向
自己紹介
松田尚也(24)
3年目エンジニア最近やったこと・やっていること
- スマホアプリ向けバックエンドの開発(Node.js)
得意・好き
- JavaScript
- Node.js
- CI/CD (Azure)
- 認証/認可 (OAuth2.0/OpenID Connect)
- ビール(アサヒビール派)
What is 関数型指向?
Q 関数型指向ってどんなイメージ???
- Javaでのラムダ + Steam関数
- 第一級関数(関数の戻り値に関数を扱うことができる言語・関数)
- haskell、F#、Scala等の関数型言語でないと書けない
- カリー化とかとか。。。
⇓
これらは、関数型指向プログラミングを行う上での要素
であって関数型指向の本質ではない
What is 関数型指向?
関数型プログラミングとは、
純粋関数を宣言的に評価することである。純粋関数は、外部から観測可能な副作用を回避することで不変性を持つプログラムを生成する
By JavaScript関数型プログラミング 本
What is 関数型指向?
- 宣言的プログラミング
- 参照透過性(純粋関数と副作用)
- 不変性
宣言的プログラミング
宣言的プログラミング
宣言型プログラミングとは、
どのような処理をしたいのかという宣言のみを行い、宣言した処理を行う。命令や手続きを明示することなく処理を表現するというパラダイム
???
宣言型プログラミング
宣言型プログラミング言語であるSQLのクエリ
SELECT name FROM game_character;「どのようなデータがほしいのか」という宣言のみしかしておらず、
「どうやってデータを取得してくるか」のデータアクセス部分の処理は行っていない
宣言型プログラミング
for文
const arr = [0, 1, 2, 3, 4, 5] for(const i = 0; i < arr.length; i++) { // 処理を行うためのデータの流れ arr[i] = Math.pow(arr[i], 2); // 行いたい処理 } console.log(arr) // [0, 1, 4, 9, 25]⇓
宣言型
const result = [0, 1, 2, 3, 4, 5].map(function(num) { return Math.pow(num, 2); }); console.log(result); // [0, 1, 4, 9, 25]ループ処理は
map
関数に委譲し、プログラミングの本質的な処理のみを記載しているちなみにアロー関数を使うと更にシンプルになります
const result = [0, 1, 2, 3, 4, 5].map(num => Math.pow(num, 2)); console.log(result) // [0, 1, 4, 9, 25]
宣言型プログラミング
宣言型プログラミングとは、
どのような処理をしたいのかという宣言のみを行い宣言した処理を行う、命令や手続きを明示することなく処理を表現するプログラミングの本質的な処理のみを記載し、可読性、再利用性が上がる
iterationというデータを扱う必要がなくなるため、状態がなくなりバグがおきにくくなる
純粋関数と副作用
純粋関数と副作用
関数型プログラミングは、純粋関数を使用し、不変性をもつプログラミング
という前提
- 関数への入力値(引数)のみに依存し、関数の呼び出し時および実行中に状態が変化する秘匿化された値や、外部スコープの値には依存しない
- 関数の外の値を一切変更しない。グローバルオブジェクトや参照渡しされた引数を変更しない
副作用のある関数
var counter = 0 function increment() { return ++counter // 関数の外(グローバルスコープの値)の状態を変更している }副作用のない関数
function increment(num) { return ++num }参照透過性がある
同じ入力に対して同じ作用と同じ出力を持つプログラム(関数)になること
参照透過性があると
リファクタリング・拡張性が高い
参照透過性があるということは、同じInputに対して必ず同じ結果が返却されるということ。
それ故に、呼び出し元を書き換える際にデグレが起きる可能性が減らせる
不変性
一見副作用がないコードに見えたとしても。。。
const sortDesc = function(arr) { return arr.sort(function(a, b) { return b - a; }); }; const arr = [1, 2, 3, 4, 5]; const result = sortDesc(arr); console.log(result); // [5, 4, 3, 2, 1] console.log(arr); // [5, 4, 3, 2, 1]
sort
関数はInputの配列をソートするため、副作用のある関数である
そのため、sort関数を参照透過性がある関数にするためにはconst sortDesc = function(arr) { return arr.slice().sort(function(a, b) { // slice関数で配列のコピー return b - a; }); }; const arr = [1, 2, 3, 4, 5]; const result = sortDesc(arr); console.log(result); // [5, 4, 3, 2, 1] console.log(arr); // [5, 4, 3, 2, 1]
What is 関数型指向?
関数型プログラミングとは、
純粋関数を宣言的に評価することである。
純粋関数は、外部から観測可能な副作用を回避することで不変性を持つプログラムを生成する
Why 関数型指向?
Why 関数型指向?
- 純粋関数である
-> InputとOutputが必ず一意になるため関数自体のテストがしやすい
-> 関数自体がモジュールとなり、自然と再利用性が高くなる
-> 状態を管理する必要がないため、非同期で起こるバグが減らせる
おしまい
- 投稿日:2019-11-12T17:06:45+09:00
redirect_toで間接的にpostリクエストを出す方法
はじめに
redirect_toを用いてpostリクエストでリダイレクトしたいが、仕様上不可能とのこと。
ただどうしても、上記形を取りたい!という方向けに、”力ずく”で間接的にpostリクエストを出す実装方法を考えましたので記します。流れ
まず仕様上、redirect_toで直接postリクエストを出すことはできないので、getリクエストを出すこととします。
下記はトーナメント方式のアプリを実装時に、1回戦で負けた際に2回戦の試合結果を自動的にデータベースに保存するという流れを取っています。1回戦で敗北した際に、first_round_controllerのrecordアクションから、second_round_controllerのloseアクションをgetリクエストでリダイレクトさせています。first_rounds_controller.rbclass FirstRoundsController < ApplicationController def record (省略) difference = @RRecord.point_difference if difference > 0 redirect_to new_player_second_round_path #1回戦勝利時(getリクエスト) else redirect_to lost_player_second_rounds_path #1回戦敗北時(getリクエスト) end end endsecond_rounds_controller.rbclass SecondRoundsController < ApplicationController def new #1回戦勝利時(getリクエスト) end def lose #1回戦敗北時(getリクエスト) end def lose_record #1回戦で敗北した時にSecondRoundテーブルに自動的にデータをセーブ(postリクエスト) SecondRound.create( カラム1: "hoge", カラム2: "hoge", カラム3: "hoge", カラム4: "hoge" ) end end敗北時、getリクエストを出すためloseアクションに伴う、lose.html.hamlが表示されますが、こちらには下記のように記述するだけで画面は何も記載されていない白画面のままです。form_withのurlはsecond_rounds_controllerのlose_recordアクション(postリクエスト)を指定します。
lose.html.haml= form_with url: lost_record_player_second_rounds_path, method: :postこのままではlose.html.hamlの白画面で止まったままですので、form_withで指定したurlに遷移するようにJSを以下のように設定します。
page_transition.js$(document).on("turbolinks:load", function(){ if (location.href.match("/[a-zA-Z0-9_]+/lose$")){ document.forms[0].submit(); } })今回はページのパスが....../loseの時にlose.html.hamlのform_withがsubmitされて、second_rounds_controllerのlose_recordアクション(postリクエスト)が呼び出されることになります。これでredirect_toで間接的にpostリクエエストを出すことができます。
まとめ
以上がredirect_toで間接的にpostリクエストを出す方法です。getリクエストを出したのちにpostリクエストを出すため、一時的に画面を読み込んでいるようなページ(白画面)が表示されますが、目的は達成できるかと思います。他に良い方法がありましたらぜひご教示ください。
参考
- 投稿日:2019-11-12T15:17:50+09:00
コピペで作るTypeScript + React Native + React Navigation + Redux + Firebaseのユーザー認証
このテンプレートはReact Navigation公式サイトのユーザー認証のページがベースになっていて、そこにfirebaseとreduxを組み込んだ感じになります。
ついでにクラスコンポーネントから関数コンポーネントへの変更もしています。
準備
テンプレートの作成
react-native init MyApp --template react-native-template-typescriptnpm install
react-native-gesture-handler、react-native-reanimated、react-native-screensは直接使っているわけではありませんが、react-navigationが依存しているようなのでインストールします。
yarn add firebase \ react-native-gesture-handler \ react-native-reanimated \ react-native-screens \ react-navigation \ react-navigation-redux-helpers \ react-redux \ redux \ styled-componentsyarn add -D @types/react-redux @types/styled-componentsFirebaseの設定
今回はiOSだけ対応するので「アプリを追加」でiOSアプリを追加します。(Firebaseがとても親切なので順に沿って進めてください)
ディレクトリを作る
今回は以下のようなディレクトリ構成ですが、好みに合わせて変更してもらえればいいと思います。
index.tsx src ├── App.tsx ├── components │ └── index.tsx ├── models │ └── firebase.ts ├── navigations │ ├── App.ts │ ├── Auth.ts │ └── AuthLoading.ts └── pages ├── App │ └── Main.tsx ├── Auth │ ├── Signin.tsx │ └── Signup.tsx └── AuthLoading └── AuthLoading.tsx一つずつ作るのは、面倒なので準備したコマンドでサクっと作ります。
mkdir -p src \ src/components \ src/models \ src/navigations \ src/pages \ src/pages/App \ src/pages/Auth \ src/pages/AuthLoadingtouch src/App.tsx \ src/components/index.tsx \ src/models/firebase.ts \ src/navigations/App.ts \ src/navigations/Auth.ts \ src/navigations/AuthLoading.ts \ src/pages/App/Main.tsx \ src/pages/Auth/Signin.tsx \ src/pages/Auth/Signup.tsx \ src/pages/AuthLoading/AuthLoading.tsxindex.tsx(エントリーポイント)
import { AppRegistry } from 'react-native' import { App } from './src/App' import { name as appName } from './app.json' AppRegistry.registerComponent(appName, () => App)App.tsx
react-navigationの公式のredux-helpersのREADMEをコピペして微修正しています。詳しくはリンクを参考にしてください。
https://github.com/react-navigation/redux-helpers一旦は動けばよかったので、stateはanyにしてreducerも分けていません。
import React from 'react' import { createSwitchNavigator, } from 'react-navigation' import { createReduxContainer, createReactNavigationReduxMiddleware, createNavigationReducer, } from 'react-navigation-redux-helpers' import { createStore, applyMiddleware, combineReducers, } from 'redux'; import { Provider, connect } from 'react-redux' import { AuthLoadingStack } from './navigations/AuthLoading' import { AuthStack } from './navigations/Auth' import { AppStack } from './navigations/App' const AppNavigator = createSwitchNavigator({ AuthLoading: AuthLoadingStack, Auth: AuthStack, App: AppStack }, { initialRouteName: 'AuthLoading', }) const navReducer = createNavigationReducer(AppNavigator) const appReducer = combineReducers({ nav: navReducer }) const middleware = createReactNavigationReduxMiddleware( (state: any) => state.nav, ); const AppContainer = createReduxContainer(AppNavigator); const mapStateToProps = (state: any) => ({ state: state.nav, }) const AppWithNavigationState = connect(mapStateToProps)(AppContainer); const store = createStore( appReducer, applyMiddleware(middleware), ); export defaults const App = () => { return ( <Provider store={store}> <AppWithNavigationState /> </Provider> ) }components
このindex.tsxは最小テンプレートです。新しくコンポーネントを作る時は、これをコピペ、リネームして作ってます。(今回は使用しないので飛ばしても大丈夫です)
index.tsx
import React from 'react' import { View, Text } from 'react-native' import { NavigationInjectedProps } from 'react-navigation' type NavigateProps = {} interface Props extends NavigationInjectedProps<NavigateProps> {} export const Foo: React.FC<Props> = (props) => { return ( <View> <Text>Foo</Text> </View> ) }models
firebase.ts
Firebaseを使うにはapiKey, projectId, messagingSenderId, appIDが必要になってきます。画像を見ながら、必要なものを記述していってください。
import firebase from 'firebase' const projectId = 'your project' export const config = { apiKey: 'xxxxxxxxxxxxxxxxxxxxxxx', authDomain: `${projectId}.firebaseapp.com`, databaseURL: `https://${projectId}.firebaseio.com`, projectId, storageBucket: `${projectId}.appspot.com`, messagingSenderId: 'xxxxxxxxxxxxxxxxxxx', appID: "xxxxxxxxxxxxxxxxxxx", } firebase.initializeApp(config); type Auth = (email: string, password: string) => Promise<firebase.auth.UserCredential> export const signup: Auth = (email, password) => { return new Promise((resolve, reject) => { firebase.auth().createUserWithEmailAndPassword(email, password) .then((user: firebase.auth.UserCredential) => { resolve(user) }) .catch((error) => { reject(error) }) }) } export const signin: Auth = (email, password) => { return new Promise((resolve, reject) => { firebase.auth().signInWithEmailAndPassword(email, password) .then((response: firebase.auth.UserCredential) => { resolve(response) }) .catch((error) => { resolve(error) }) }) }navigations
App.ts
import { createSwitchNavigator } from 'react-navigation' import { Main } from '../pages/App/Main' export const AppStack = createSwitchNavigator({ Main: { screen: Main }, })Auth.ts
import { createSwitchNavigator } from 'react-navigation' import { Signin } from '../pages/Auth/Signin' import { Signup } from '../pages/Auth/Signup' export const AuthStack = createSwitchNavigator({ Signin: { screen: Signin }, Signup: { screen: Signup }, })AuthLoading
import { createSwitchNavigator } from 'react-navigation' import { AuthLoading } from '../pages/AuthLoading/AuthLoading' export const AuthLoadingStack = createSwitchNavigator({ AuthLoading: { screen: AuthLoading } })pages
Main.tsx
import React, { useCallback } from 'react' import { View, Text, TouchableOpacity, AsyncStorage } from 'react-native' import { NavigationInjectedProps } from 'react-navigation' import styled from 'styled-components/native' type NavigateProps = {} interface Props extends NavigationInjectedProps<NavigateProps> {} export const Main: React.FC<Props> = (props) => { const _handleOnClickSignOut = useCallback(async () => { AsyncStorage.removeItem('uid') props.navigation.navigate('Auth') }, []) return ( <Container> <Text>Main</Text> <LinkContainer> <TouchableOpacity onPress={_handleOnClickSignOut}> <Text>ログアウト</Text> </TouchableOpacity> </LinkContainer> </Container> ) } const Container = styled.View` flex: 1; align-items: center; justify-content: center; ` const LinkContainer = styled.View` align-items: center; justify-content: center; `Signin.tsx
import React, { useState, useCallback } from 'react' import { View, Text, TouchableOpacity, TextInput, AsyncStorage } from 'react-native' import { NavigationInjectedProps } from 'react-navigation' import { signin } from '../../models/firebase' import styled from 'styled-components/native' type NavigateProps = {} interface Props extends NavigationInjectedProps<NavigateProps> {} export const Signin: React.FC<Props> = (props) => { const [email, setEmail] = useState<string>('') const [password, setPassword] = useState<string>('') const _handleOnSubmit = useCallback(async () => { const res: firebase.auth.UserCredential = await signin(email, password) if (res.user) { AsyncStorage.setItem('uid', res.user.uid) props.navigation.navigate('App') } }, [email, password]) const _handleOnClickSignUp = useCallback(async () => { props.navigation.navigate('Signup') }, []) return ( <Container> <View> <Text>ログイン</Text> <View> <View> <Text>メールアドレス</Text> <TextInput onChangeText={(value) => setEmail(value)} value={email} placeholder="メールアドレスを入力してください" placeholderTextColor="#aaa" autoCompleteType="email" /> </View> <View> <Text>パスワード</Text> <TextInput onChangeText={(value) => setPassword(value)} value={password} placeholder="****" placeholderTextColor="#aaa" autoCompleteType="password" /> </View> </View> <View> <Button onPress={_handleOnSubmit}>送信</Button> </View> </View> <LinkContainer> <TouchableOpacity onPress={_handleOnClickSignUp}> <Text>新規登録</Text> </TouchableOpacity> </LinkContainer> </Container> ) } const Container = styled.View` flex: 1; align-items: center; justify-content: center; ` const LinkContainer = styled.View` align-items: center; justify-content: center; `Signup.tsx
import React, { useState, useCallback } from 'react' import firebase from 'firebase' import { View, Text, TouchableOpacity, TextInput, AsyncStorage } from 'react-native' import { NavigationInjectedProps } from 'react-navigation' import { signup } from '../../models/firebase' import styled from 'styled-components/native' type NavigateProps = {} interface Props extends NavigationInjectedProps<NavigateProps> {} export const Signup: React.FC<Props> = (props) => { const [email, setEmail] = useState<string>('') const [password, setPassword] = useState<string>('') const _handleOnSignIn = useCallback(async () => { const res: firebase.auth.UserCredential = await signup(email, password) if (res.user) { AsyncStorage.setItem('uid', res.user.uid) props.navigation.navigate('App') } }, [email, password]) const _handleOnClickSignIn = useCallback(async () => { props.navigation.navigate('Signin') }, []) return ( <Container> <View> <Text>新規登録</Text> <View> <View> <Text>メールアドレス</Text> <TextInput onChangeText={(value) => setEmail(value)} value={email} placeholder="メールアドレスを入力してください" placeholderTextColor="#aaa" autoCompleteType="email" /> </View> <View> <Text>パスワード</Text> <TextInput onChangeText={(value) => setPassword(value)} value={password} placeholder="****" placeholderTextColor="#aaa" autoCompleteType="password" /> </View> </View> <View> <Button onPress={_handleOnSignIn}>送信</Button> </View> </View> <LinkContainer> <TouchableOpacity onPress={_handleOnClickSignIn}> <Text>ログイン</Text> </TouchableOpacity> </LinkContainer> </Container> ) } const Container = styled.View` flex: 1; align-items: center; justify-content: center; ` const LinkContainer = styled.View` align-items: center; justify-content: center; `AuthLoading.tsx
import React, { useEffect } from 'react' import { NavigationInjectedProps } from 'react-navigation' import { View, Text, AsyncStorage } from 'react-native' type NavigateProps = {} interface Props extends NavigationInjectedProps<NavigateProps> {} export const AuthLoading: React.FC<Props> = (props) => { useEffect(() => { AsyncStorage.getItem('uid').then((uid) => { props.navigation.navigate(uid ? 'App' : 'Auth') }).catch((error) => { console.error(error) props.navigation.navigate('Auth') }) }, []) return ( <View> <Text>AuthLoading</Text> </View> ) }まとめ
仕組みとしては、アプリが立ち上がったらまずAuthLoadingというコンポーネントが呼ばれます。
AuthLoadingでAsyncStorageがuid
を持っていれば、Main.tsxが呼ばれ、なければログインページが呼ばれます。これは自分用のテンプレートで、現在動いているものから余分なものを削ぎ落として書きました。
なのでもしかしたら、コピペしただけだとエラーが出てしまうかもしれません。(未確認)参考
https://qiita.com/F_PEI/items/64b54f9075805dc0e312
https://reactnavigation.org/docs/en/auth-flow.html
- 投稿日:2019-11-12T14:08:43+09:00
【初心者】JavaScript 配列を操作するメソッド(push,forEach,find,filter,map)【備忘録14】
11/10~11/11に勉強したこと
push
既にある配列の最後に新しい要素を追加するメソッド。
【例】const numbers = [1,2,3]; console.log(numbers); numbers.push(4); console.log(numbers);【出力】
[1,2,3]
[1,2,3,4]【!】
numbers.push(4)を書く際に「=」を付けないように気を付ける!fouEach
配列の要素1つずつ取り出し、全要素に繰り返し同じ処理を行うメソッド。
【例】const numbers=[1,2,3]; numbers.forEach((number)=>{ console.log(number); )};【出力】
1
2
3【!】
- {console.log...}の箇所で改行をすることで見やすくなるので癖づける。
- forEachの後の()の中のことをコールバック関数と呼ぶ。
- 一番最後の「;」を忘れやすいので、()や{}を書いたときに先に「;」を書く。find
コールバック関数に記した条件に合う<1つ目の要素>を取り出す。
【例1】const numbers=[1,3,5,7]; const foundNumber=numbers.find((number)=>{ return number > 4 ; });【出力】
5 (1つ目のみなので7は出力されない)【例2】
const animals=[ {id:1,name:"犬"}, {id:2,name:"猫"} ]; const foundAnimal=animals.find((animal)=>{ return animal.id ===1; }); console.log(foundAnimal);【出力】
{id:1,name:"犬"} (オブジェクトそのものが出力される)【!】
- animals.findの「.」を忘れやすいので気を付ける!
- return の後を「animal」だけにしてしまわない!「animal.id」or「animal.name」
- 条件式の語尾に「;」の付け忘れがあったので気を付ける!filter
findの全要素ver. (記した条件に合う要素すべてを取り出す)
【例】const numbers=[1,2,3,4,5]; const filteredNumbers=numbers.filter((number)=>{ return number % 2 === 0; }); console.log(filteredNumbers);【出力】
[2,4]map
配列内のすべての要素に処理⇒その戻り値から新しい配列を作る。
【例1】const numbers=[1,2,3]; const doubledNumbers = numbers.map((number)=>{ return number * 2; }); console.log(doubledNumbers);【出力】
[2,4,6]【例2】
const names=[ {firstName="Misa",lastName="Yamada"}, {firstName="Hana",lastName="Tanaka"} ]; const fullNames=names.map((name)=>{ return name.firstName + name.lastName ; }); console.log(fullNames);【出力】
[Misa Yamada,Hana Tanaka]【!】
returnの後の「name.firstName」を「names.firstName」と何度か
してしまったのでコールバック関数の引数でしっかり書くよう気を付ける。振り返り・今後について
まだJavaScriptの章が一つ残っていますが、今までしてきたことが
すべて覚えきれているかとても不安です。
できるだけリセットして復習をしたり、ここで出力したり
別ノートでまとめを見直したりはしていますが
もし他に何か良い復習法があれば教えてほしいです。一人で勉強しているので、めげないように此処に書き記して
行くのが当面の目標です。毎日時間を作って今後も頑張ります!
- 投稿日:2019-11-12T14:03:48+09:00
三項演算子の説明と三項演算子の例題
今回はJavaScriptで使われてる三項演算子の説明と、HTML上で入力した数字が奇数か偶数かを判断するページを作ってみます。
三項演算子とは?
「?」と「:」を組見合わせて、if~else文のような処理を簡略化したものです。
書き方
条件式?式1:式2ページの制作
JavaScriptをHTMLに繋げる方法を知ってる前提で説明します。
JavaScriptのソース
function checkEven() { var v = document.getElementById("number").value; var r = document.getElementById("result"); if(v.length == 0) { r.textContent = "何かの入力値が必要です"; } else if(isNaN(v)){ r.textContent = v + "は数字ではありません"; } else { r.textContent = (v%2==0) ? "偶数です":"奇数です"; } }HTMLのソース
<h1>奇数か偶数かを判断します。</h1> <input id="number" /> <button onclick="checkEven()">入力判定</button> <p id="result"></p>引用 : Dripcoke
- 投稿日:2019-11-12T11:50:32+09:00
JSでHTMLのvideoタグをコントロールで頭出し再生
jqueryで指定したら上手くいかなかったので
バニラJSしたら上手くできたメモ。。HTML
<video id="video-target" autoplay muted playsinline loop poster="/assets/img/video.png"> <source src="/assets/mov/video.mp4" type="video/mp4" /> <img src="/assets/img/video.png" /> </video>JS
var media = document.getElementById("video-target"); if (media) { console.log(media); media.currentTime = 0; //先頭へ }JS(jQuery)
var media = $("#video-target"); if (media) { console.log(media); media.currentTime = 0; //先頭へ }参考サイト
- 投稿日:2019-11-12T11:08:53+09:00
【TypeScript/JavaScript】Optional Chainingのトランスパイル後がわからなかったので調べてみた
こちらの記事で、TypeScript
v3.7からの新機能「Optional Chaining」が解説されていました。
その中に「Optional Chaining」を使用したコードのトランスパイル結果が記載されています。例えばこんなコードがあるとして
interface Foo { name: string bar: { name: string baz?: { name: string } } } let foo: Foo foo = { name: 'a', bar: { name: 'bar' } } console.log(foo.bar.baz?.name)トランスパイルするとどうなるかというと下記のようになります。
var _a; let foo; foo = { name: 'a', bar: { name: 'bar' } }; console.log((_a = foo.bar.baz) === null || _a === void 0 ? void 0 : _a.name);このトランスパイル後のソース、特に4行目のコード
console.log((_a = foo.bar.baz) === null || _a === void 0 ? void 0 : _a.name);がどのような文法で成り立っているかわからなかったので、調査してみました。
※: 記事内に間違っている箇所があればコメントで教えてくださるとたすかります。
前提条件
以下の文法については理解している前提とします。
==
と===
の違い- 三項演算子
ポイント
- 代入式の返り値は「代入した値」
void 0
はundefined
と等価代入式の返り値は「代入した値」
まず前半部分について注目してみます。
(_a = foo.bar.baz) === nullJavaScriptでは、代入式の返り値は「代入した値」になります。つまり、
(_a = foo.bar.baz)
の返り値はfoo.bar.baz
です。
また、interface Foo
の定義により 、foo.bar.baz
の型はTypeScript上ではstring|undefined
であるため、
左辺(_a = foo.bar.baz)
の返り値は何らかの文字列
もしくはundefined
です。
その返り値の値をnull
と厳密比較している、というのが上式の意味です。ここで、
===
による厳密比較がtrue
になるには両辺の型が一致している必要がありますが、
string|undefined
とnull
では型がそれぞれ異なるためこの式の結果は必ずfalse
になります。つまりこの式は、
_a
にfoo.bar.baz
を代入した上で、||
以降の式を必ず実行する仕掛けになっています。
void 0
はundefined
と等価次に後半部分です。
_a === void 0 ? void 0 : _a.name
void 0
という見慣れない文法がありますが、これはundefined
と等価です。
上式をundefined
で置き換えるとわかりやすくなります。_a === undefined ? undefined : _a.name
_a
がundefinedならundefined
、そうでないなら_a.name
を返す式ですね。
undefined
でなくvoid 0
を使用するのは、「undefined
が書き換え可能である」というのが理由の一つとしてあると思います。なぜundefinedの代わりに使うのか?
jsではundefinedは値であり、すなわち上書きができる。そのため代わりとして、voidが使われる。
exampleconsole.log(a === undefined) // true console.log(a === void 0) // true // undefinedをうわがく var undefined = 100 console.log(a === undefined) // false console.log(a === void 0) // true
void 0
を使用することでundefined
の書き換えがトランスパイルに影響されないようになっています。まとめ
以上より、トランスパイル後のコードは「
_a
にfoo.bar.baz
を代入し、_a
がundefined
でなければ_a
のプロパティname
を返す。そうでなければundefined
を返す」という挙動であることがわかりました。(※ でも、
foo.bar.baz
を直接比較せずに_a
に代入してから比較するのはなんでだろう。。。。分かる人いたら教えてください。)
- 投稿日:2019-11-12T10:40:30+09:00
【簡単】ブックマークレットを使って、口コミを5秒で調べる!!【お手軽】
職場でブックマークレットの話が出てきたものの
え?え?何それ??便利なの??
説明聞いてもイメージ湧かない....となり、茫然としたので、触ってみることに。
結論
$\huge{めちゃ便利でした?}$
ブックマークレットって?
ブックマークレットとは、ブラウザのブックマークにJSのコードを登録しておき、
クリックすることで実行できる技術です。結構前からあったみたいですね。
具体的にどんなことできんの?って方は、
下の実例集みると、想像が膨らみます実例集
- URLをHTML形式でコピー
- URLをMarkdown形式でコピー
- 日本語を含むURLをエンコードせずにコピー
- YouTubeを保存する
- 次/前のページへ
- ページの読み込み速度を計測する
- ページのHTMLチェック
- Webページで使われているフォントを調べる
- ページのレスポンシブチェック
などなど、便利なブックマークレットが紹介されていますね。
https://psephopaiktes.github.io/pages/bookmarklets/#bookmarklet-4
jsの知識とアイデア次第で、色々できそうです。
口コミ検索を5秒で実装する
ところで、皆さん口コミ調べる時ってどうしてますか?
私は、基本
Twitter です。
Twitter で 「商品名 デメリット」 「商品名 良い」
などで検索することが多いです。というわけで、今回は、
Twitterで検索を簡単にすること
を目標に作成していきます。使い方と実装
ブックマークレットの良いところは、
環境構築なし
”ブックマークに登録する”だけで、プログラムが走ることですね。実装
下記のリンクのボタンを、
ドラッグアンドドロップでブックマークバーに移動させるだけ!See the Pen bookmarklet for twitter by naoto190131 (@190131start) on CodePen.
5秒で実装できましたね☺️
使い方
こんな形で、ブックマークに保存しておき、
ポップアップに、検索したいワードを打ち込めば...
Twitterの検索画面で、口コミを検索することができます!
便利っ...!
コード
<button> <a href="javascript:!function(undefined){ var q = prompt('単語を入力し、Enterキーを押してください',''); var preference = ' 最高 '; if(q === '' || q === null){window.alert('キャンセルされました'); }else{window.open('https://twitter.com/search?q='+q+preference+'&src=typed_query');} }();">Twitter メリット</a> </button>コードはこんな感じ
href属性にjsを書くだけですね。
ブックマークバーに、ドラッグアンドドロップで移動させて使いたかったので
buttonにしてますが、aタグだけでもいいです。別タブで開かせたかったので、window.open()使いましたが、
同じタブ内での遷移ならwindow.location.hrefにすると良いと思います。あとはPreferenceを変更するだけで、
悪い口コミも作成できます。<button> <a href="javascript:!function(undefined){ var q = prompt('単語を入力し、Enterキーを押してください',''); var preference = ' 最悪 '; if(q === '' || q === null){window.alert('キャンセルされました'); }else{window.open('https://twitter.com/search?q='+q+preference+'&src=typed_query');} }();">Twitter デメリット</a> </button>今後の課題
Chromeのコンテンツブロックに引っかかるので、
どなたか、回避方法教えてください...
- 投稿日:2019-11-12T10:23:08+09:00
jquery.ui.touch-punch.jsをSurfaceのタッチパネルでも使えるようにする
概要
jQuery UIをタッチイベントに対応させるライブラリに
jquery.ui.touch-punch.jsがあるが、
surfaceのタッチイベントでは動かないので
jquery.ui.touch-punch.jsを修正する。施策
$.support.touch = 'ontouchend' in document;上記スクリプトの下に追加
$.support.touch = 'ontouchend' in document; //追加 if('onpointerenter' in window){ $.support.touch = true; }解説
「Ignore browsers without touch support」の行で
タッチデバイス以外は処理を中断するスクリプトが記述されているため、
ポインタデバイスであるsurfaceでは動かなくなる。// Detect touch support $.support.touch = 'ontouchend' in document; if('onpointerenter' in window){ $.support.touch = true; } // Ignore browsers without touch support if (!$.support.touch) { return; }
- 投稿日:2019-11-12T00:14:13+09:00
[ kintone devCamp 復習コンテンツ ] Step3 kintone REST API 編
こちらは、「ステップで学ぶ kintone カスタマイズ勉強会」 kintone devCamp Step3 kintone REST API の復習コンテンツです。
? はじめに
Step3 では、アプリ間データ操作や 他サービス連携で使える kintone REST API の概念と書き方を勉強しました。
今回も知識を定着させるために、復習問題をご用意しましたので、ぜひチャレンジしてみてください(`・∀・´)? 目次
- 事前準備
- 問題チャレンジ!
- 問題1(難易度⭐️):ボタンクリック時に出張概要のフィールドデータを引き継いでレコード新規追加
- 問題2(難易度⭐️⭐️):レコード追加と編集画面での保存成功後にプロセスを次に回す
- ステップアップ情報
? 事前準備
- Google Chrome ブラウザ
- システム管理権限のある kintone アカウント
- kintone 出張申請 & 旅費精算申請アプリ
- JSEdit for kintone プラグイン
- ご自身の kintone 環境にプラグインを追加した上で、アプリテンプレートを追加すると自動的にアプリにもプラグインが適用されます。
- プラグインのダウンロードがまだな方はこちらをご参照ください。
- Google Chrome ブラウザの開発者ツール
- エラーが出た時のデバッグ(検証)に使います。
? 問題チャレンジ!
問題1(難易度⭐️)
出張申請アプリのレコード詳細画面にボタンを設置し、それをクリックした時に出張の内容を引き継いで、旅費精算申請アプリにレコードを新規追加するプログラムを記述してください。
※ 出張申請 & 旅費精算申請アプリはアプリテンプレートより追加
※ 今回作成の JavaScript ファイルは、出張申請アプリに適用実装イメージ
※ アクション機能(標準機能)を使うと、別アプリへのデータコピーと同時にレコード新規追加画面が立ち上がります。今回は新規追加画面を表示せずにデータ登録のみを行いたいので、 kintone REST API を使います。考え方
プログラムを書く前に、まずは手順を頭の中で組み立ててみましょう。
- 実行したいのは、出張申請アプリのレコード詳細画面を開いた時。
- 詳細画面にボタンを作成する。
- ボタンをクリックしたら出張アプリの一部データを旅費精算申請アプリにコピーしてレコードを追加する。
- 成功したら成功メッセージ、失敗したらエラーメッセージを表示する
カスタマイズ
手順が分かったら、以下の穴埋めプログラムの×マークを埋めていきましょう。
穴埋め.js(function() { 'use strict'; // レコード追加関数 var appId = ×××; // 旅費精算申請アプリのアプリID function postRecord(event) { // パラメータ定義 var params = { app: appId, record: { 申請者: { value: [ { code: event.record.申請者.value.code } ] }, 上長: { value: [ { code: event.record.承認者.value[0].code } ] }, 行き先: { value: event.record.出張先.value }, 出発: { value: event.record.出発.value }, 帰着: { value: event.record.帰着.value } } }; // 成功時の関数 function handleSuccess() { window.alert('旅費申請アプリにレコード登録しました!'); } // 失敗時の関数 function handleError(error) { window.alert('エラーが発生しました。\nエラー内容:' + error.message); } // POSTメソッドの実行 kintone.api(kintone.api.url('×××', true), '×××', ×××, ×××, ×××); } // ボタンクリックでのレコード追加 function clickButton(event) { // ボタン要素の作成 var button = document.createElement('button'); // ボタン要素に id を追加 button.id = 'button'; // ボタン内のテキストを指定 button.innerText = '旅費申請アプリに登録'; // ボタン周りの余白を指定(上, 右, 下, 左) button.style.margin = '20px 0px 20px 20px'; // ボタンクリック時の処理 button.onclick = function() { postRecord(event); }; kintone.app.record.getHeaderMenuSpaceElement().appendChild(button); } // レコード詳細画面表示イベントで clickButton 関数を実行 kintone.events.on('app.record.×××', ×××); })();? ヒント
JSEdit for kintone プラグインでの開発方法
POST リクエストをしたいので、params 変数で登録したいデータを指定する。
? 答え
正解.js(function() { 'use strict'; // レコード追加関数 var appId = ×××; // 旅費精算申請アプリのアプリID function postRecord(event) { // パラメータ定義 var params = { app: appId, record: { 申請者: { value: [ { code: event.record.申請者.value.code } ] }, 上長: { value: [ { code: event.record.承認者.value[0].code } ] }, 行き先: { value: event.record.出張先.value }, 出発: { value: event.record.出発.value }, 帰着: { value: event.record.帰着.value } } }; // 成功時の関数 function handleSuccess() { window.alert('旅費申請アプリにレコード登録しました!'); } // 失敗時の関数 function handleError(error) { window.alert('エラーが発生しました。\nエラー内容:' + error.message); } // POSTメソッドの実行 kintone.api(kintone.api.url('/k/v1/record', true), 'POST', params, handleSuccess, handleError); } // ボタンクリックでのレコード追加 function clickButton(event) { // ボタン要素の作成 var button = document.createElement('button'); // ボタン要素に id を追加 button.id = 'button'; // ボタン内のテキストを指定 button.innerText = '旅費申請アプリに登録'; // ボタン周りの余白を指定(上, 右, 下, 左) button.style.margin = '20px 0px 20px 20px'; // ボタンクリック時の処理 button.onclick = function() { postRecord(event); }; kintone.app.record.getHeaderMenuSpaceElement().appendChild(button); } // レコード詳細画面表示イベントで clickButton 関数を実行 kintone.events.on('app.record.detail.show', clickButton); })();レコード詳細画面にて、ボタンクリックで旅費精算申請アプリにレコードが追加されていれば成功です。
アクションボタン利用の場合は、対象アプリのレコード新規追加画面がデータコピーされた状態で表示されますが、このカスタマイズの場合は特に画面遷移はしません。
? 解説
- 旅費精算申請アプリに新規レコードを作成するための postRecord 関数を作成
- POST メソッドを使うため、params という変数でリクエストボディを定義
- コールバック関数とエラーバック関数を定義
- POST メソッドのリクエスト先 URL を指定してリクエストを記述
- postRecord 関数をボタンクリック時に実行するための clickButton 関数を作成
- ボタン要素を作成して、レコード詳細画面のヘッダースペースに設置
- button.onclick メソッドを使って、postRecord 関数を実行
- レコード詳細画面を表示した時のイベントを指定して、clickButton 関数を実行
- ボタン用の要素作成には、document.createElement メソッドを利用
- ボタンの中の文字入力には、innerText メソッドを利用
問題2(難易度⭐️⭐️)
旅費精算申請アプリのレコード追加画面と編集画面での保存成功後に、ステータスが「未申請」の場合、プロセスを次に回す(「申請」アクションを実行する)プログラムを記述してください。
※ 今回作成の JavaScript ファイルは、旅費精算申請アプリに適用実装イメージ
考え方
プログラムを書く前に、まずは手順を頭の中で組み立ててみましょう。
- 実行したいのは、旅費精算申請アプリのレコード追加と編集画面で保存が成功した後。
- ステータスが未処理の場合以外は、何もしないようにする。
- ステータスが未処理の場合は、「申請」アクションを実行する。
- 成功したら何もしない、失敗したらエラーメッセージを表示する。
カスタマイズ
手順が分かったら、以下の穴埋めプログラムの×マークを埋めていきましょう。
穴埋め.js(function() { 'use strict'; // レコード追加と編集画面の保存成功後に、プロセスを次に回す function putRecord(event) { // パラメータ定義 var params = { app: event.appId, id: event.recordId, action: '×××', assignee: event.record.上長.value[0].code }; // ステータスが未処理の場合以外は何もしない if (event.record.ステータス.value !== '未処理') { return; } // 成功時の関数 function handleSuccess() { return event; } // 失敗時の関数 function handleError(error) { window.alert('エラーが発生しました。手動で申請ボタンをクリックしてください。\nエラー内容:' + error.message); } // PUTメソッドの実行 kintone.api(kintone.api.url('×××'), '×××', ×××, ×××, ×××); } // レコード追加画面と編集画面の保存成功後イベントで putRecord 関数を実行 kintone.events.on([ 'app.record.×××', 'app.record.×××' ], putRecord); })();? ヒント
- プロセスを次に回す API は「レコードのステータスの更新」記事を参照する。
- その他必要となるパラメータは同じなので、順に設定していく。
- イベントは新規追加と編集とどちらも効くように 2つ設定するので、配列の形式で指定する。
? 答え
正解.js(function() { 'use strict'; // レコード追加と編集画面の保存成功後に、プロセスを次に回す function putRecord(event) { // パラメータ定義 var params = { app: event.appId, id: event.recordId, action: '申請', assignee: event.record.上長.value[0].code }; // ステータスが未処理の場合以外は何もしない if (event.record.ステータス.value !== '未処理') { return; } // 成功時の関数 function handleSuccess() { return event; } // 失敗時の関数 function handleError(error) { window.alert('エラーが発生しました。手動で申請ボタンをクリックしてください。\nエラー内容:' + error.message); } // PUTメソッドの実行 kintone.api(kintone.api.url('/k/v1/record/status'), 'PUT', params, handleSuccess, handleError); } // レコード追加画面と編集画面の保存成功後イベントで putRecord 関数を実行 kintone.events.on([ 'app.record.create.submit.success', 'app.record.edit.submit.success' ], putRecord); })();旅費精算申請アプリのレコード追加/編集画面の保存後にステータスが「申請中」に回れば、カスタマイズ完了です!
? 解説
- レコード追加と編集画面の保存成功後イベントを指定して、putRecord 関数を実行
- プロセスを次に進める putRecord 関数を作成
- ステータスの更新なので、PUT メソッドを利用
- params 変数に更新したい内容を入れて、リクエストボディを定義
- コールバック関数とエラーバック関数を定義
- PUT メソッドのリクエスト先 URL を指定してリクエストを記述
? ステップアップ情報
お疲れ様でした!ラスト回、どうでしたでしょうか?
すらすらっと書けるようになるには、まずは地道に数をこなすところからです!他にも予習復習にお使いいただけるコンテンツがありますので、ご紹介です。ぜひチャレンジしてみてください〜
- cybozu developer network はじめようシリーズ
- kintone API 編(全12回)
![]()
- cybozu developer network Tips
Step1~3 までお疲れ様でした!!
Enjoy kintone customization!