- 投稿日:2022-03-03T23:42:58+09:00
日時ライブラリのTemporalの色々なAPIをいじってみた
はじめに Temporalは現状 TC39 proposal stage 3 のものを使用しています 今後APIが変わる可能性もあるのでご注意ください JavaScriptで日時ライブラリを使用する機会があり、Temporalが stage 3 だったのを思い出したので、少し触ってみようと思いました。 せっかくなので、何か作りながら触ってみようということで、簡易的なカレンダーを作ってみました。 Temporalとは JavaScript標準ライブラリへの追加を目指して開発中の日時ライブラリです。 Temporalについては日本語でもドキュメントを一部翻訳いただいていたり、Qiitaにすでにいい記事がいくつかあるので、詳しくはそちらをご覧ください。 こちらは Stage 2 時点のものなのでAPIが変わっているものがありますが、Temporalとはなにか?というのがわかりやすいです。 stage 3 時点での各APIについて説明されています つくったもの 色々なAPIを使いたいと思い、カレンダーを作りました。 (ソースコードはこちら) インストール js-temporal/temporal-polyfillのREADMEを参考にライブラリをインストールします。 APIの詳細 (随時追加予定) 今回いじった部分をいくつかピックアップして使い方を説明していきます。 現在の日時 Temporal.Nowで現在の日時を取得できます。 Object の関係図にあるのですが、TemporalにはPlainDateTimeやPlainDate, PlainMonthDayなどのオブジェクトがあり、最初使い方に迷います。 どれを使えばいいかは以下の画像1を参考にするとわかりやすいです。 Temporal.Now.zonedDateTimeISO().toString() // 2022-03-03T22:59:43.755983755+09:00[Asia/Tokyo] Temporal.Now.zonedDateTimeISO().toLocaleString() // 2022/3/3 22:59:43 JST Temporal.Now.plainDateISO().toString() // 2022-03-03 Temporal.Now.plainDateISO().toLocaleString() // 2022/3/3 また以下のように各種プロパティを使うことで、year,monthなどの値を取得することができます。 日付の計算 PlainDateTimeやPlainDateなどのオブジェクトには、add, subtractといったメソッドが用意されています。 const today = Temporal.Now.plainDateISO() const tomorrow = today.add({ days: 1 }) const yesterday = today.subtract({ days: 1 }) また、比較はequalsやcompareを使います。 compareの戻り値は −1, 0, 1 なので、これで並び替えができます。 Temporal.PlainDate.compare(today, tomorrow) // -1 Temporal.PlainDate.compare(today, today) // 0 Temporal.PlainDate.compare(today, yesterday) // 1 今回は使わなかったのですが、期間を計算するuntil,sinceなどもあります。 また、withを使うと、日付だけ固定するなどの日時を作ることができるのも便利そうです。 const today = Temporal.Now.plainDateISO() const firstDayOfMonth = today.with({ day: 1 }) フォーマットして表示 文字列として表示をしたいときに、toJSON, toString, toLocaleStringなどを使うことができます。 これらのメソッドのオプションで表示方法を指定することができます。 Temporal.Now.plainTimeISO().toString({ smallestUnit: 'minute', roundingMode: 'floor', }) // 23:25 Temporal.Now.plainDateISO().toLocaleString(undefined, { dateStyle: 'medium', }) // 2022/03/03 また、他に Intlというモジュールも含まれており、これを使ってもformatができるようです。 const dateTimeFormat = new Intl.DateTimeFormat() console.log(dateTimeFormat.format(Temporal.Now.plainDateTimeISO())) // 2022/3/4 9:38:22 console.log(dateTimeFormat.format(Temporal.Now.plainDateISO())) // 2022/3/4 console.log(dateTimeFormat.format(Temporal.Now.plainDateTimeISO().toPlainTime())) // 9:40:24 その他 Cookbookに色々な事例付きで使い方が乗っているので、参考にしてみてください。 タイムゾーンについてはこちらで説明されています。 まとめ Temporalは機能が豊富すぎて、最初どう使えばいいのか分からなかったのですが、基本のオブジェクトについてはわかってきました。 もう少し触りながら、Temporalの概念や細かいAPIの仕様なども調べてみようと思います。 参考 tc39/proposal-temporal: Provides standard objects and functions for working with dates and times. js-temporal/temporal-polyfill: Polyfill for Temporal (under construction) Temporal documentation Temporal walkthrough - Google スライド JavaScriptの日時処理はこう変わる! Temporal入門 - Qiita Temporal で JavaScript の次世代の日時処理に触れてみる - Qiita https://tc39.es/proposal-temporal/docs/ja/index.html より引用 ↩
- 投稿日:2022-03-03T22:57:52+09:00
MediaRecorderで作ったp5.jsの超簡単録画ツール(p5.MovRec)を更新した話
サマリ? 以前に、MediaRecorderとffmpeg.wasmでp5.jsの超簡単録画ツール(p5.MovRec)を作った話 で作ったツールについて、セキュリティの都合上ffmpeg.wasmが使えなくなるケースが多くなってしまったため、再度、高画質webm動画を作るツールとして再リリースすることにしました。録画手順もさらに簡単になったので、改めて記事化しておきます。 というわけで、あらためまして、MediaRecorderを使って、p5.js上で、簡単にスケッチの録画ができるツール(p5.MovRec)を開発しました。既存のp5.jsのスケッチを汚すことなく、HTMLファイルにたった1行追加して、キーボードを押すだけで爆速で動画が生成される簡単ツールとなっています。TwitterとかYouTubeとかに作品をアップされている方にお勧めです。 ※ただし、TwitterにUpする方は、Convertioなどでwebmからmp4へ変換する必要はあります。 成果物 p5MovRec: A simple movie recording tool for p5.js 2022/3/3 現在のversionは1.0.0です。 きっかけ 僕はよく自分のp5.jsの作品を動画にしてTwitterにアップすることが多いのですが、以前にp5.jsで作ったジェネラティブアートをtwitterに動画で貼り付ける際のメモにも書いたように、きれいな動画作品としてTwitterへ投稿するのは割と大変です。 静止画を切り出して、PC上のffmpegで連結して数分間レンダリングを待つ・・・という大変手間と時間がかかることをしていたわけです。 2022年の現在も、SNS上でもどうやって動画化していますか?という問いは定期的にみられますが、完全なソリューションはでてきていないなぁという実感があります。 また、特にTwitterとかが顕著ですが、アップしてもエンコードされてしまうので、個人的には画像がそこまできれいでなくてもよいから、もっと手間も時間もかからない動画化方法はないだろうか?とずっと考えていました。 もともとのコンセプトはMediaRecorderを使って生成した動画を、ffmpeg.wasmという『ffmpegをブラウザ上で実行できるツール』を使ってトランスコードすることがメインのツールでしたが、諸事情からトランスコードは断念し、簡単に動画を生成できるツールに特化して今に至ります。 簡単な使い方 実行環境 PC(Win/Mac) 環境のみを想定 MediaRecorderはたぶん結構いろんなブラウザで動くのかなーとは思いつつ、Google Chromeのみを対象とさせてください。 なお、このツールはP5 EditorとOpenProcessingの両方でも正しく動作します!Webのサービスの中から、即時動画が生成される様子は『未来が来たな』という印象を受けます。 ライブラリのインポート p5.jsスクリプトの後に、以下の1行を追加するだけです。終了。 <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.js"></script> <!-- INSERT HERE --> <script src="https://tetunori.github.io/p5MovRec/dist/1.0.0/basic/p5MovRec.js"></script> もう、すぐRecできる 1キーを押すと録画開始です。デバッグコンソールに?Start Recording.のテキストが表示されるはずです。画面の中から勝手にcanvasを探してそこだけrecしてくれています。 次に、2キーを押すと録画が終了し、即時YYYYMMDDhhmmss.webmという名前の動画が生成されます! 文字通り、秒で生成されます。 こちらもコンソールに✅Recorded.のテキストが表示されます。このwebmフォーマットは聞きなれないかもしれないのですが、Chromeブラウザでは確実に再生ができると思います。 このツールは内部でkeyPressed()を使っているので、もしすでにスケッチ内でkeyPressed()を使っている場合はそちらで上書きされてしまいます。よって、本ツールを使うときは、以下のように録画開始終了の関数をkeyPressed()内で呼んでください。インスタンスp5MovRecとそのメソッドstartRec()、 stopRec()とsetMovType()を使用します。また、 もちろん、p5.jsのsetup関数の中で呼べば最初のフレームから録画することも可能ですよ。 function keyPressed() { switch (keyCode) { case 49: //1: Start record p5MovRec.startRec(); break; case 50: //2: Stop record p5MovRec.stopRec(); break; default: break; } } キー操作の表 キー 説明 1 録画開始。 2 録画終了。Webmフォーマットの動画を生成。 サンプル Basic Sample On GitHub Basic Sample On OpenProcessing 注意! canvasについて このツールはcreateCanvas()で生成されたcanvasのstreamから動画を作成するため、#つぶやきProcessingや#p5tの作品においては、createCanvas()をsetup()の模範的な位置に動かす必要あるかも。例えば、下記のように。 Starndard #つぶやきProcessing code: t=0 draw=_=>{ createCanvas(w=720,w) noStroke(fill('#つぶやきProcessing')) ... Please change as: t=0 w=720 setup=_=>{ createCanvas(w,w) } draw=_=>{ noStroke(fill('#つぶやきProcessing')) ... Tips 動画品質について フレームレート 出力動画のフレームレートが気になる場合もあると思います。PCなどの環境都合で、フレームレートが速くなりすぎたりということもままありました。そのような場合は、p5.jsのスケッチ上で、 frameRate()を調整してみてください。frameRate(30)、frameRate(40)、frameRate(60)とかで上限を設定してあげると所望の動画が手に入りやすくなります。 Canvasのサイズ これはTwitter向けの話ですが720がベストにエンコードされる値かと思います。ブラウザの拡縮機能で大きさが変わっていると、この拡縮値が結果に影響することもありますので、動画のクオリティが気になる際は、Zoomを100%にもどしたり、80%に小さくしたりなどで改善する可能性があります。出力動画の縦横サイズもチェックしてみてください。 リファレンス MediaRecorder https://developer.mozilla.org/en-US/docs/Web/API/MediaRecorder
- 投稿日:2022-03-03T20:51:19+09:00
FileAPIを上手く使い「タイピングソフト」を作ろう!:JavaScrpt初級~中級者向け
Youtubeで公開した内容を記事にしました。 タイピングソフトを作ろう!【後編】「HTML/CSS/JavaScript/PHP...のキータイピング練習にも対応」JavaScript入門 以下Youtubeでの解説動画も張っておきます。(同じ内容ですが、詳しすぎるほど説明してます(笑)) ◆ サンプルファイル Github からダウンロードしてご利用ください https://github.com/yamazakidaisuke/keytyping_youtube ◆ どんなキータイピングアプリ? 外部ファイルでキーワードを管理します、以下のように並べるだけ。 これはJavaScriptでFileApiを利用することで可能になります。 以下画面構成を見ていただければわかりますが、 上記のテキストファイルを読み込めば、オリジナルのキーワードをタイピングできます!! ◆ 変数を準備 keytyping.html //*************************************** //グローバル変数 //*************************************** const G = { read_file : document.getElementById("file"), //File選択 output_text : document.getElementById("output_text"), //キーワード表示 input_text : document.getElementById("input_text"), //キーワード入力 start : document.getElementById("start"), //スタートボタン total : document.getElementById("total"), //点数表示 texts : [], //配列でキーワード入ってくる //読み込んだキーワードを配列で保持 score : 0 //点数を保持 } ◆関数(処理)を記述 keytyping.html //*************************************** //関数 //*************************************** //ファイルを選択読み込む(FileAPIを利用) function readFile(e){ const file = e.target.files[0]; //ファイル情報を代入 const reader = new FileReader(); //インスタンス化 reader.onload = function(){ //読込み完了後にloadする G.texts = reader.result.split("\n"); //配列変換して代入 }; reader.readAsText(file); //textFileをRead } //テキストをランダムに表示 function shuffleText(){ G.input_text.value=""; //入力文字を削除 const rf = Math.random() * G.texts.length; const r = Math.floor(rf); //整数の乱数を作成 G.output_text.value = G.texts[r]; //n番目テキストを表示 } //キーを押すたびに処理 function keyEvent(e){ if(G.output_text.value === G.input_text.value){ shuffleText(); //ランダムにテキストを再表示 G.score++; //スコアに+1 G.total.textContent = G.score; //表示を入れ替える } } ◆ イベントと関数の紐づけ keytyping.html //*************************************** //イベントと関数の紐づけ //*************************************** G.start.onclick = shuffleText; G.read_file.onchange = readFile; G.input_text.onkeydown = keyEvent; 以下Youtube動画で詳しく解説してるので、どうぞ見てください! ◆ どうでしたか? そこそこできる人は上記の解説・コードで十分だったかと思いますが、 初学者であれば、動画で詳しくやってみてください。 以上
- 投稿日:2022-03-03T20:51:19+09:00
FileAPIを使い「テキストファイルでキーワード変更可能」なタイピングソフトを作ろう!:JavaScrpt初級~中級者向け
Youtubeで公開した内容を記事にしました。 タイピングソフトを作ろう!【後編】「HTML/CSS/JavaScript/PHP...のキータイピング練習にも対応」JavaScript入門 以下Youtubeでの解説動画も張っておきます。(同じ内容ですが、詳しすぎるほど説明してます(笑)) YouTube https://youtu.be ◆ サンプルファイル Github からダウンロードしてご利用ください https://github.com/yamazakidaisuke/keytyping_youtube ◆ どんなキータイピングアプリ? 外部ファイルでキーワードを管理します、以下のように並べるだけ。 これはJavaScriptでFileApiを利用することで可能になります。 以下画面構成を見ていただければわかりますが、 上記のテキストファイルを読み込めば、オリジナルのキーワードをタイピングできます!! image.png ◆ 変数を準備 keytyping.html //*************************************** //グローバル変数 //*************************************** const G = { read_file : document.getElementById("file"), //File選択 output_text : document.getElementById("output_text"), //キーワード表示 input_text : document.getElementById("input_text"), //キーワード入力 start : document.getElementById("start"), //スタートボタン total : document.getElementById("total"), //点数表示 texts : [], //配列でキーワード入ってくる //読み込んだキーワードを配列で保持 score : 0 //点数を保持 } ◆関数(処理)を記述 keytyping.html //*************************************** //関数 //*************************************** //ファイルを選択読み込む(FileAPIを利用) function readFile(e){ const file = e.target.files[0]; //ファイル情報を代入 const reader = new FileReader(); //インスタンス化 reader.onload = function(){ //読込み完了後にloadする G.texts = reader.result.split("\n"); //配列変換して代入 }; reader.readAsText(file); //textFileをRead } //テキストをランダムに表示 function shuffleText(){ G.input_text.value=""; //入力文字を削除 const rf = Math.random() * G.texts.length; const r = Math.floor(rf); //整数の乱数を作成 G.output_text.value = G.texts[r]; //n番目テキストを表示 } //キーを押すたびに処理 function keyEvent(e){ if(G.output_text.value === G.input_text.value){ shuffleText(); //ランダムにテキストを再表示 G.score++; //スコアに+1 G.total.textContent = G.score; //表示を入れ替える } } ◆ イベントと関数の紐づけ keytyping.html //*************************************** //イベントと関数の紐づけ //*************************************** G.start.onclick = shuffleText; G.read_file.onchange = readFile; G.input_text.onkeydown = keyEvent; 以下Youtube動画で詳しく解説してるので、どうぞ見てください! ◆ どうでしたか? そこそこできる人は上記の解説・コードで十分だったかと思いますが、 初学者であれば、動画で詳しくやってみてください。 以上
- 投稿日:2022-03-03T20:51:19+09:00
JavaScript入門:タイピングソフトを作ろう!「 キーワード簡単編集で自分の練習したい文字列に変更可能 」
Youtubeで公開した内容を記事にしました。 タイピングソフトを作ろう!【後編】「HTML/CSS/JavaScript/PHP...のキータイピング練習にも対応」JavaScript入門 以下Youtubeでの解説動画も張っておきます。(同じ内容ですが、詳しすぎるほど説明してます(笑)) ◆ サンプルファイル Github からダウンロードしてご利用ください https://github.com/yamazakidaisuke/keytyping_youtube ◆ どんなキータイピングアプリ? 外部ファイルでキーワードを管理します、以下のように並べるだけ。 これはJavaScriptでFileApiを利用することで可能になります。 以下画面構成を見ていただければわかりますが、 上記のテキストファイルを読み込めば、オリジナルのキーワードをタイピングできます!! ◆ 変数を準備 keytyping.html //*************************************** //グローバル変数 //*************************************** const G = { read_file : document.getElementById("file"), //File選択 output_text : document.getElementById("output_text"), //キーワード表示 input_text : document.getElementById("input_text"), //キーワード入力 start : document.getElementById("start"), //スタートボタン total : document.getElementById("total"), //点数表示 texts : [], //配列でキーワード入ってくる //読み込んだキーワードを配列で保持 score : 0 //点数を保持 } ◆関数(処理)を記述 keytyping.html //*************************************** //関数 //*************************************** //ファイルを選択読み込む(FileAPIを利用) function readFile(e){ const file = e.target.files[0]; //ファイル情報を代入 const reader = new FileReader(); //インスタンス化 reader.onload = function(){ //読込み完了後にloadする G.texts = reader.result.split("\n"); //配列変換して代入 }; reader.readAsText(file); //textFileをRead } //テキストをランダムに表示 function shuffleText(){ G.input_text.value=""; //入力文字を削除 const rf = Math.random() * G.texts.length; const r = Math.floor(rf); //整数の乱数を作成 G.output_text.value = G.texts[r]; //n番目テキストを表示 } //キーを押すたびに処理 function keyEvent(e){ if(G.output_text.value === G.input_text.value){ shuffleText(); //ランダムにテキストを再表示 G.score++; //スコアに+1 G.total.textContent = G.score; //表示を入れ替える } } ◆ イベントと関数の紐づけ keytyping.html //*************************************** //イベントと関数の紐づけ //*************************************** G.start.onclick = shuffleText; G.read_file.onchange = readFile; G.input_text.onkeydown = keyEvent; 以下Youtube動画で詳しく解説してるので、どうぞ見てください! ◆ どうでしたか? そこそこできる人は上記の解説・コードで十分だったかと思いますが、 初学者であれば、動画で詳しくやってみてください。 以上
- 投稿日:2022-03-03T20:07:49+09:00
休憩時間だからこそ、テンションをあげたい時に押すボタンを作った
「疲れた!」という言葉を何かのエネルギーに変えたい リモワ中、誰もいないのをいいことについ「疲れたー!!!」と叫んだりしたことはないだろうか?私はある。実際は「つっっっっっっかれたー……」という、心の底から絞り出した渾身の「疲れた」。 この言葉を 何かのエネルギーに変換することはできないだろうか? そう、例えば テンションをあげる何かに…… テンションをあげるものと言えば テンションが上がるものは何か? 氷川きよしでしょ。 完成したのがこちら。 ※音声は流れません アプリ <使い方> マイク認証をONにして、ボタンを押す。 心の底から「疲れたー!」と言う。(「めっちゃ疲れた」「ちょー疲れた」でも可) 氷川きよしさんが煽り散らしてくれるので、こぶしを振り上げる。 ※隠しコマンドとして「マツケン」と言うと、例のあの曲が流れます。 コード See the Pen 休憩時間だからこそ、テンションをあげたい時に押すボタン2 by mshita (@morishitanana) on CodePen. 音声認識(認識後、判定あり) JS // 音声を認識できたら this.recog.onresult = (event) => { // 認識されしだい、this.resultにその文字をいれる // Vueなので、文字をいれただけで画面表示も更新される this.result = event.results[0][0].transcript; //アクション1 if(this.result.indexOf('疲れた') > -1){ window.location.href = 'https://www.youtube.com/watch?v=2fmucSKJXxQ&t=53s'; }; //アクション2 if(this.result.indexOf('マツケン') > -1){ window.location.href = 'https://www.youtube.com/watch?v=XazyhnymUQo&t=64s'; }; }; // 音声認識が終了したら this.recog.onspeechend = () => { this.recog.stop(); this.recogButton = '停止(クリックして再開)'; }; // 認識できなかったら this.recog.onerror = () => { this.result = '(認識できませんでした)'; this.recog.stop(); this.recogButton = '停止(クリックして再開)'; }; }, 音声認識した結果、『疲れた』『マツケン』という単語を含んでいた場合のみ指定のURLにジャンプする。 単語は完全一致ではなく、部分一致で判定している。 <完全一致させたい場合> if(this.result.indexOf('疲れた') > -1){ を、if(this.result === '疲れた'){ に変更する。 Youtube動画の、再生開始箇所を指定したい場合 流したい動画の ここから! というところで右クリックをして『現時点の動画のURLをコピー』を選択する。その箇所から再生されるURLが保存される。 まとめ 理想を言えば、仕事を開始したと同時に起動して「疲れたー」と言ったタイミングで、きよしを流したい。 が、Web Speech APIは基本的に数秒で切れてしまう。起動したままにするには、HTTPSサーバーが必要とのこと。 (参考)https://monomonotech.jp/kurage/iot/webspeechapi_voice_recognition.html それほど時間をかけずにお手軽に作りたかったというのもあるので、今回は断念。
- 投稿日:2022-03-03T19:10:37+09:00
JavaScript let constについての備忘録
改善点 会社でもコメントでも御指摘あった通り殆ど日記と大差なくQiitaで書く様な物では無かったのと自分が思ってるよりか説明下手なのもあったので今回からはよりエンジニア向け且つ分かり易く纏める事を意識したいと思います、御指摘して下さった方々本当にありがとうございます。 そんな訳で本日の学習内容 午前中 主にHTMLの学習 HTMLの基本文法から、HTMLの始まりを表す<!DOCTYPE html>タグや見出しの<h>タグ、段落を表す<p>タグと箇条書きに用いる<li>タグにその<li>タグを囲む<ul>と兎に角タグ、タグ、タグと出て来ますが基本的にはタグとタグで挟むイメージと各タグの要素を少しでも覚えておけば案外混乱無くコーディング出来るのでしょうか?一先ずは<body>タグの間に本文を書き込むって事を頭に入れて今後の学習を進めてみましょうか。 午後 JavaScriptの学習 letでは無くvarが出て来てそれでいて使い方は同じ?取り敢えず調べて見て分かった事は varは再代入、再宣言が可能な事 var a = 1 var a = 2 // みたいな感じに使える letは再宣言がvarみたいに出来ない let a = 1 let a = 2 // が出来ない、再代入は出来るので間違えない様にしたい所。 グローバル変数、ローカル変数?これまた知らない単語が出て来たので調べました。 グローバル変数 関数の外側にある変数でプログラムが終わるまで値は保持される。 ローカル変数 関数の内側にある変数で関数が実行される度に初期値になる この辺は事前にプログラムの基礎知識を学習していれば使い方はスンナリ理解出来るかも。 そしてinnerHTML、これは取得したHTMLの中身を書き換える命令との事。 例えば事前にHTML側のidに画像1を入れてJavaScript側の操作でgetElementByIdでidを取得した後にこのinnerHTMLを使って画像2に書き換える処理を実装出来る訳で。 相変わらず分かりづらいとは思いますが大きく間違ってはいないはず。 はい、本日の学習は以上となります。 今後ももっと分かり易くエンジニア向けに纏められる様に改善致しますので宜しくお願いします。
- 投稿日:2022-03-03T19:10:37+09:00
入社3日目
改善点 会社でもコメントでも御指摘あった通り殆ど日記と大差なくQiitaで書く様な物では無かったのと自分が思ってるよりか説明下手なのもあったので今回からはよりエンジニア向け且つ分かり易く纏める事を意識したいと思います、御指摘して下さった方々本当にありがとうございます。 そんな訳で本日の学習内容 午前中 主にHTMLの学習 HTMLの基本文法から、HTMLの始まりを表す<!DOCTYPE html>タグや見出しの<h>タグ、段落を表す<p>タグと箇条書きに用いる<li>タグにその<li>タグを囲む<ul>と兎に角タグ、タグ、タグと出て来ますが基本的にはタグとタグで挟むイメージと各タグの要素を少しでも覚えておけば案外混乱無くコーディング出来るのでしょうか?一先ずは<body>タグの間に本文を書き込むって事を頭に入れて今後の学習を進めてみましょうか。 午後 JavaScriptの学習 letでは無くvarが出て来てそれでいて使い方は同じ?取り敢えず調べて見て分かった事は varは再代入、再宣言が可能な事 var a = 1 var a = 2 // みたいな感じに使える letは再宣言がvarみたいに出来ない let a = 1 let a = 2 // が出来ない、再代入は出来るので間違えない様にしたい所。 グローバル変数、ローカル変数?これまた知らない単語が出て来たので調べました。 グローバル変数 関数の外側にある変数でプログラムが終わるまで値は保持される。 ローカル変数 関数の内側にある変数で関数が実行される度に初期値になる この辺は事前にプログラムの基礎知識を学習していれば使い方はスンナリ理解出来るかも。 そしてinnerHTML、これは取得したHTMLの中身を書き換える命令との事。 例えば事前にHTML側のidに画像1を入れてJavaScript側の操作でgetElementByIdでidを取得した後にこのinnerHTMLを使って画像2に書き換える処理を実装出来る訳で。 相変わらず分かりづらいとは思いますが大きく間違ってはいないはず。 はい、本日の学習は以上となります。 今後ももっと分かり易くエンジニア向けに纏められる様に改善致しますので宜しくお願いします。
- 投稿日:2022-03-03T16:28:23+09:00
Moment.jsで現在日付からの年度末日を計算する
要件 現在日付の年度末の日付を出力したい。 例えば1月1日なら 同年の3月31日だが、4月1日以降は翌年の3月31日を出力すること。 if文を使わず、なるべくモダンなコードで実装すること。 コード nendoEnd(){ // まず現在の年度を取得する const y = moment().subtract(3, "months").year(); // 現在の年度の年に1を足して 3月の31日にする // ここで注意が必要なのは、month()で設定できるデータはゼロから始まる数値だということです。 const now = moment() .year(y + 1) .month(2) .endOf('month'); return now.format("YYYY-MM-DD"); } テスト 2022-03-31 → 2022-03-31 2022-04-01 → 2023-03-31 OK! 参考 現在の年度は、3ヶ月前の日付の年を取得することで得られる。 https://qiita.com/togana/items/d622f2e38c67dddba99d
- 投稿日:2022-03-03T15:39:07+09:00
【JavaScript】ページ遷移せずに画面遷移する
0. 広告: 【僕は男だから戦場に閉じ込められた】ウクライナの男性出国禁止措置はジェンダー平等に反しています。 男性差別に反対するため署名にご協力ください。 https://change.org/MensRights18 1. やりたいこと 有向グラフで表される任意の状態遷移をブラウザの画面上に実現したい。 具体的には、画面上に、「現在のノード」を表示し、さらに現在のノードを始点とする全エッジを、 ボタンとして描画する。ボタンをクリックすると、そのエッジに従って次のノードへ移動する。 但しこの際の移動では、ページ遷移を行わない。あくまでも1ファイルで完結する。 2. 仕様 headタグで画面シングルトンを定義し、bodyタグで呼び出す。 画面シングルトンは、DOMを用いてdocument.bodyを書き換える機能を持つ。 有向グラフはheadタグにて書いておく。 3. コーディング 次の順番で行う。 ノードクラスの定義 有向グラフクラスの定義 有向グラフ(とノード)のテスト 画面シングルトンの定義 画面シングルトンのテスト 3-1. ノードクラスの定義 ノード.js export var ノード = { new: function(内容, 子たち){ return { __子たち: 子たち, __内容: 内容, __型: "ノード", 子: function(エッジ名){ try{ if(this.__子たち[エッジ名].__型 == "ノード") return this.__子たち[エッジ名]; else throw ""; }catch(error){ throw "エッジ名が見つからないか、または子ノードに異常がありました"; } }, 内容: function(){return this.__内容;} }; } }; 3-2. 有向グラフクラスの定義 有向グラフクラスのコンストラクタに、次の文法でコードを与えると、有向グラフが表現されるものとする。 <コード> ::= (<ノード> ( <ノード> "to" <ノード> "as" <文字列 except="{{{">)*)? <ノード> ::= "{{{" <文字列 except="}}}"> "}}}" 没(始ノードが異なるなら、エッジ名の衝突を許可すべき) 有向グラフ.js import {ノード} from "./ノード.js"; export var 有向グラフ = { new: function(コード){ コード = コード.replaceAll("\r", "").replaceAll("\n", ""); var 開始ノード名, 始点ノード名, 終点ノード名, エッジ名; [開始ノード名, コード] = 開始ノード名を取得する(コード); var エッジ情報たち = {}; while(コード.match("{{{")) { [始点ノード名, 終点ノード名, エッジ名, コード] = エッジ名を取得する(コード); エッジ情報たち[エッジ名] = [始点ノード名, 終点ノード名]; } var ノードオブジェクトたち = ノードオブジェクトたちを求める(開始ノード名, エッジ情報たち); return { __開始ノード: ノードオブジェクトたち[開始ノード名], 最初のノード: function(){return this.__開始ノード;} }; } }; var 開始ノード名を取得する = function(コード){ var arr = コード.match(/^[ \t\r\n]*{{{(.*?)}}}(.*)$/); arr.shift(); return arr; }; var エッジ名を取得する = function(コード){ var arr = コード.match(/^[ \t\r\n]*{{{(.*?)}}}to{{{(.*?)}}}as[ \t\r\n]*(.+?)[ \t\r\n]*({{{.*)?$/i); arr.shift(); if (arr[3] == undefined) arr[3] = ""; return arr; }; var ノードオブジェクトたちを求める = function(開始ノード名, エッジ名から始ノード名と終ノード名への辞書){ // ①ノード名をすべて集める var ノード名たち = [開始ノード名]; for (var [エッジ名, [始ノード名, 終ノード名]] of Object.entries(エッジ名から始ノード名と終ノード名への辞書)) { if(!(ノード名たち.includes(始ノード名))) ノード名たち.push(始ノード名); if(!(ノード名たち.includes(終ノード名))) ノード名たち.push(終ノード名); } // ②重複除去 // (処理不要) // ③「ノード名から子たちへの辞書」を作る var ノード名から子たちへの辞書 = {}; var ノードたち = {}; for(var ノード名 of ノード名たち) ノードたち[ノード名] = ノード.new(ノード名, {}); for(var ノード名 of ノード名たち) { ノード名から子たちへの辞書[ノード名] = {}; for (var [エッジ名, [始ノード名, 終ノード名]] of Object.entries(エッジ名から始ノード名と終ノード名への辞書)) if(始ノード名 == ノード名) ノード名から子たちへの辞書[ノード名][エッジ名] = ノードたち[終ノード名]; } // ④「ノード名からノードオブジェクトへの辞書」を作る var ノード名からノードオブジェクトへの辞書 = {}; for (var [ノード名, 子たち] of Object.entries(ノード名から子たちへの辞書)) { var ノードobj = ノードたち[ノード名]; ノードobj.__子たち = 子たち; ノード名からノードオブジェクトへの辞書[ノード名] = ノードobj; } // ⑤ノードオブジェクトたちを返却する return ノード名からノードオブジェクトへの辞書; }; 3-3.有向グラフ(とノード)のテスト 3-1節および3-2節の通りノード.jsと有向グラフ.jsを作成して同じディレクトリに格納し、さらに次のようなindex.htmlを作って同じディレクトリに格納しよう。そしてそのディレクトリはサーバにアップロードする。 ローカルで同じ実験をするとCORSポリシーというのに引っかかって動作してくれないので注意。 index.html <script type="module"> import {有向グラフ} from "./有向グラフ.js"; var じゃんけん = 有向グラフ.new(`{{{グー}}} {{{グー}}}to{{{パー}}}as 勝つ {{{グー}}}to{{{チョキ}}}as 負ける {{{チョキ}}}to{{{グー}}}as 勝つ {{{チョキ}}}to{{{パー}}}as 負ける {{{パー}}}to{{{チョキ}}}as 勝つ {{{パー}}}to{{{グー}}}as 負ける `); ; console.log(じゃんけん); </script> サーバにアップロードしたら、ブラウザから サーバ/ディレクトリ/index.html にアクセスし、 ブラウザの開発者ツールから開いてコンソールを開く。 chromeブラウザでやると、上図のように表示されるはずだ。 「▶」をクリックすると、メンバを一覧で見ることが出来る。この機能を使って、じゃんけんの有向グラフがちゃんと表現されていることを確認できる。 例えば、「勝つ」をたどって、「グー」→「パー」→「チョキ」→「グー」→...の循環が表現されていることを確認したのが次の図である。 あるいは、console.log(じゃんけん);の行にブレークポイントを設置してブラウザを一時停止させたのち、 次のように対話方式でjavascriptコードを実行することにより確認することもできる。 3-4. 画面シングルトンの定義 index.htmlと同じディレクトリに次のような画面.jsを置く。 画面.js export var 画面 = { __現在のノード: undefined, 表示する: function(ノードobj){ try{ if(!(ノードobj.__型 == "ノード")) throw ""; }catch(thrown) { throw "ノードが異常です。"; } this.__現在のノード = ノードobj; document.body = document.createElement("body"); var span_node = document.createElement("span"); span_node.setAttribute("id", "node"); span_node.innerHTML = ノードobj.内容(); var span_edge = document.createElement("span"); span_edge.setAttribute("id", "edge"); var button; for (var [エッジ名, 子ノードobj] of Object.entries(ノードobj.__子たち)) { button = document.createElement("input"); button.setAttribute("type", "button"); button.setAttribute("value", エッジ名); button.onclick = function(){画面.表示する(ノードobj.子(this.value))}; span_edge.appendChild(button); 子ノードobj = null; } document.body.appendChild(span_node); document.body.appendChild(document.createElement("hr")); document.body.appendChild(span_edge); } }; ちなみに上記コード中のbutton.onclick = function(){画面.表示する(ノードobj.子(this.value))};におけるthisは、押されたボタンに対応するinput要素オブジェクトを指す。 したがって、この文脈でthis.valueはエッジ名を指す。 3-5. 画面シングルトンのテスト index.htmlを次のように書き換える index.html <head><script type="module"> import {有向グラフ} from "./有向グラフ.js"; import {画面} from "./画面.js"; var じゃんけん = 有向グラフ.new(`{{{グー}}} {{{グー}}}to{{{パー}}}as 勝つ {{{グー}}}to{{{チョキ}}}as 負ける {{{チョキ}}}to{{{グー}}}as 勝つ {{{チョキ}}}to{{{パー}}}as 負ける {{{パー}}}to{{{チョキ}}}as 勝つ {{{パー}}}to{{{グー}}}as 負ける `); 画面.表示する(じゃんけん.最初のノード()); </script></head> その後ブラウザからindex.htmlにアクセスすると、次のように表示、画面遷移が起き、有向グラフオブジェクトに従った画面遷移が実現したことを確認できる。
- 投稿日:2022-03-03T15:06:52+09:00
Stripe Connectで構築したマーケットプレイスで、コンビニ決済に対応する3つの方法(デスティネーション支払い編)
Stripeを利用することで、顧客は料金の支払いをコンビニで行えます。また、Stripeユーザーは、コンビニでの決済処理結果を、Stripe DashboardやWebhookを通して受け取ることができます。 コンビニ決済を有効化することで、これまでリーチすることが困難だった「クレジットカードを持たない顧客」にリーチすることが可能です。また、顧客も公共交通機関での移動中や、信頼性の低いネットワークを利用している状況など、「クレジットカードを取り出して入力する」操作がやりにくい場面でも、安心して注文ができるようになります。 コンビニ決済は、Connectでも利用可能 この決済はConnectでプラットフォームを構築されているお客様もご利用が可能です。 C2Cのマーケットプレイスでは、クレジットカード情報の取り扱いに不安を感じる顧客も少なからず存在します。プラットフォーム・マーケットプレイスがコンビニ決済をサポートすることで、顧客はより安心して商品・サービスを注文できるようになります。 コンビニ決済をConnectで構築したプラットフォームに追加する3つの方法 コンビニ決済に対応する方法は、プラットフォームに組み込まれた決済処理方法によって異なります。支払い方法に応じても異なりますので、今回はダイレクト支払いをご利用中のケースについて紹介します。 この記事では、Node.jsとReactを例に実装方法を紹介します。phpやRubyなど、その他の言語やフレームワークでの実装については、APIドキュメント(https://stripe.com/docs/api) をご確認ください。記事内で紹介しているメソッド・関数を、それぞれの言語で実行する方法を見ることができます。 1: Checkoutを利用して、決済処理を行なっている場合 Stripe Checkoutを利用して決済画面を提供している場合、組み込みはとても簡単です。 以下の例のように、payment_method_typesにkonbiniを追加するか、payment_method_typesを削除して、ダッシュボードから決済方法( https://stripe.com/docs/payments/dashboard-payment-methods )を設定します。 await stripe.checkout.sessions.create({ line_items: [{ price: 'price_xxxxxx', quantity: 1 }], success_url: 'http://localhost:3000/success', cancel_url:'http://localhost:3000/cancel', mode: 'payment', payment_method_types: ['card', 'konbini'], payment_intent_data: { application_fee_amount: 100, transfer_data: { destination: "acct_xxxx", }, }, customer_creation: 'if_required' }) 変更が完了すると、Checkoutの支払い方法に「コンビニ決済」が表示されます。 2: ElementsのPaymentElementを利用している場合 PaymentElementを利用して決済フォームを実装している場合についても、ほとんど設定の変更が必要ありません。 PaymentIntentを作成する際に、payment_method_typesを指定している場合のみ、konbiniを追加しましょう。 const paymentIntent = await stripe.paymentIntents.create({ amount: 2000, currency: 'jpy', payment_method_types: ['konbini', 'card'], application_fee_amount: 200, transfer_data: { destination: "acct_xxxx", }, }) 以下の例のように、payment_method_options['konbini']に追加情報を登録することもできます。 const paymentIntent = await stripe.paymentIntents.create({ amount: 2000, currency: 'jpy', payment_method_types: ['konbini', 'card'], application_fee_amount: 200, transfer_data: { destination: "acct_xxxx", }, payment_method_options: { konbini: { product_description: "Tシャツ", expires_after_days: 3, tax_amount: 200 } } }) あとはPaymentIntentのClient SecretをStripe.jsに渡すだけで、PaymentElementがコンビニ決済を選択できるように表示を変更します。 カードなどの処理と同様、confirmPaymentを実行すると、PaymentIntentがconfirmされます。 3: ElementsでPayment Elementを利用して「いない」場合 Elementsを利用する場合、基本的にはPaymentElementを利用することをお勧めします。 しかしサイトデザインの要件を満たすために、PaymentElement以外のElementを利用する必要があるケースでは、上記のPaymentElementを利用した実装が行えません。 Payment Elementを利用せずに実装する場合、フロントエンドにも追加の実装が必要です。 最も簡単な方法は、**「コンビニ決済を処理するためのformを、新しく用意すること」**です。 まず、「氏名・メールアドレス・電話番号」を入力するフォームを用意します。 const KonbiniForm: FC = () => { const [name, setName] = useState("") const [email, setEmail] = useState('') const [phone, setPhone] = useState('') return ( <form onSubmit={async (e)=>{ e.preventDefault() }> <div> <label>電話番号</label> <input type='tel' value={phone} onChange={e=>setPhone(e.target.value)} /> </div> <div> <label>名前</label> <input type='text' value={name} onChange={e=>setName(e.target.value)} /> </div> <div> <label>メールアドレス</label> <input type='email' value={email} onChange={e=>setEmail(e.target.value)} /> </div> <button type='submit' disabled={status === 'inprogress'}>注文する</button> </form> ) } その後、PaymentIntentを作成するAPI呼び出しと、コンビニ決済を処理するメソッドを呼び出す実装をsubmitイベントに設定します。 const KonbiniForm: FC = () => { const stripe = useStripe() const [name, setName] = useState("") const [email, setEmail] = useState('') const [phone, setPhone] = useState('') return ( <form onSubmit={async (e)=>{ e.preventDefault() try { if (!stripe) return; const intent = await fetch('/api/payment_intent', { method: 'post' }).then(data=>data.json()) const result = await stripe.confirmKonbiniPayment(intent.client_secret, { payment_method: { billing_details: { name, email, }, }, payment_method_options: { konbini: { confirmation_number: phone, }, }, } ) console.log(result) } catch(e) { console.log(e) } }}> ... confirmKonbiniPaymentが成功すると、こちらもコンビニでの支払い情報がモーダル表示されます。 もし独自のUIで情報を表示させたい場合は、以下のように引数を追加します。 const result = await stripe.confirmKonbiniPayment(intent.client_secret, { payment_method: { billing_details: { name, email, }, }, payment_method_options: { konbini: { confirmation_number: phone, }, }, }, { handleActions: false } ) handleActions: falseを設定すると、モーダルが表示されなくなります。 コンビニでの支払いのために必要な情報は、result.paymentIntent.next_actionから取得することができます。 この中には、以下の情報が含まれています。 支払い期日(konbini_display_details.expires_at) Stripeが用意する支払い方法ページのURL(konbini_display_details.hosted_voucher_url) コンビニチェーンごとの入力情報(確認番号と支払い番号) (konbini_display_details[familymart|lawson|ministop|seicomart]) コンビニのロゴ・アイコン画像などは含まれません。実装時に自分で用意する必要がありますので、ご注意ください。 コンビニ決済をサポートする際の注意点 注文完了時点では、決済が完了していません コンビニ決済での注文の場合、実際に顧客が代金を支払うまでに時間差があります。 そのため、カード決済のみ対応していたプラットフォームなどでは、「決済が完了したことを確認できるまで、発送業務等の後続処理を行わない」実装になっているかを確認しましょう。 もし決済完了を確認せずに後続の業務を行なった場合、実際には決済が完了できていない(コンビニでの支払いが実行されなかった)場合にも商品を発送してしまうなどの問題が発生します。 Checkoutを利用している場合 Checkoutを利用した決済では、まずcheckout.session.completedイベントを受け付けます。Webhookイベント内の"payment_status"をチェックし、カード決済などで支払い済みの場合(payment_status=paid)ならば後続業務を実行します。 コンビニ決済など、支払いが未完了の場合は、payment_status=unpaidですので、在庫の確保を続けるだけにするなど、決済の完了を待つシステムにする必要があります。 顧客の決済が完了した場合には、追加でcheckout.session.async_payment_succeededイベントが発生します。こちらのイベントを受け付けた場合にはじめて発送等の業務を開始させましょう。 Elements(PaymentIntent)を利用している場合 Elements(PaymentIntent)を利用している場合、カード決済でもコンビニ決済でもpayment_intent.succeededイベントの発火で後続業務を開始できます。 Webhookを利用した期限切れの処理が必要になるケースがあります コンビニ決済では、設定した期限内に支払いが行われないケースも発生します。その場合、確保している在庫を開放するなどの操作が必要になります。 この操作を実装するには、Webhookで以下のイベントを受け付ける必要があります。 Checkoutを利用したケース:checkout.session.async_payment_failed Elements(PaymentIntent)を利用したケース: payment_intent.payment_failed また、支払いが完了していない顧客に、メールを送信する仕組みも用意されています。 [設定 > メール]から設定することができますので、こちらも合わせてご利用ください。 手数料の最低金額が設定されています コンビニ決済を利用する場合、決済手数料は最低でも120円が発生します。 この手数料は、デスティネーション支払いの場合、プラットフォーム側で負担します。 そのため、手数料(application_fee)が120円を下回る場合には、決済手数料の方が高くなりますのでご注意ください。 おわりに コードを見ていただくと、Connectを利用したプラットフォームでも、コンビニ決済に対応するための追加作業は、通常の決済サービスでの対応とほぼ同じです。 コンビニ決済をサポートすることで、クレジットカードを持たない顧客でもサービスを利用できるようになりますので、ぜひご自身のプラットフォームにも導入してみてください。 [関連記事]コンビニ決済入門シリーズ [関連記事]コンビニ決済入門シリーズ Checkout / Elementsへの導入方法 Billing(サブスクリプション)への導入方法 Webhookでの処理方法
- 投稿日:2022-03-03T15:05:25+09:00
Stripe Billingを利用して、コンビニ決済での定期購入サブスクリプションを管理する(JS・ノーコード編)
定期課金においても、クレジットカード以外の決済方法をサポートすることで、より多くの顧客にリーチできます。 コンビニ決済を有効化すると、顧客は定期的な料金の支払いをコンビニで行えます。これにより、これまでリーチすることが困難だった「クレジットカードを持たない顧客」にリーチすることが可能です。 また、顧客も公共交通機関での移動中や、信頼性の低いネットワークを利用している状況など、「クレジットカードを取り出して入力する」操作がやりにくい場面でも、安心して注文ができるようになります。さらに、Stripeでサブスクリプションを集約管理することで、契約期間中にコンビニ決済とカードやその他の決済方法への切り替えも簡単に可能です。 この記事では、Billingを利用している場合に、コンビニ決済を受け付けるための方法と、実運用に備えて注意しておきたいポイントの例を紹介します。 Dashboardからの操作で、ノーコードに開始する 定期課金を最も簡単に始める方法は、Dashboardから操作することです。 実際にテスト環境でサブスクリプションを作成してみましょう。 コンビニ支払いを受け付けるサブスクリプションを作成しよう まずDashboardのメニューにある[作成]ボタンをクリックし、[サブスクリプション]を選択しましょう。*[c]キーと[s]キーを順番に押すことでも移動できます。 サブスクリプション作成ページでは、まず[顧客]セクションで、サブスクリプションを提供する顧客を選びます。もし、テスト用の顧客データが存在しない場合は、[新しい顧客を追加]を選択して、先にテスト用の顧客データを作成しましょう。 この際、顧客のメールアドレスは、[ダッシュボードのログインユーザーと同じアドレス]に設定しましょう。ログインユーザーと同じメールアドレスであれば、Dashboardからメールをテスト送信することができます。 続いて、[料金体系]セクションから、請求する料金プランを選択します。こちらも、[新しい商品を追加]から新しく商品・料金データを作成するページに移動できます。 次に[支払い方法]セクションで、支払い方法を設定しましょう。 まず、[手動支払い用の請求書を顧客にメールで送付する]を選択します。 コンビニ決済が有効化されていれば、[支払い方法]にコンビニ決済のアイコンが表示されています。表示されていない場合や、ほかの支払い方法を非表示にしたい場合は、右横の[管理]リンクをクリックして、設定を変更しましょう。 最後に、ページ上部の[テストのサブスクリプションを開始]をクリックしましょう。 サブスクリプション一覧に移動し、一覧に作成したサブスクリプションが表示されていれば、作成完了です。 作成したサブスクリプションの請求メール・支払いリンクを確認しよう 続いてどのようなメールが送信されるかを確認しましょう。 サブスクリプション一覧から、作成したサブスクリプションをクリックし、詳細ページに移動します。 ページをスクロールし、[インボイス]を見つけたら、初回の請求の行をクリックしましょう。 インボイスの詳細ページから[請求書を送信]をクリックし、メールを送信しましょう。もしテスト環境でメール送信ができない場合は、顧客のメールアドレスをDashboardにログイン中のユーザーのメールアドレスに変更します。 送信に成功すると、Stripeが顧客に送信する請求書メールが届きます。 ここで[この請求書を支払う]をクリックすると、請求書の決済ページに移動します。 コンビニ決済が有効化されている場合、決済方法の1つにコンビニ決済が表示されます。 電話番号には、[組み込みをテストする]にあるテスト用の番号を入力します。たとえば、11111111110を入力すると、3分後にコンビニでの決済が完了したものとしてStripeが処理を行います。 なお、実際の決済では、支払い処理完了後に表示される支払いコードなどを利用して、顧客がコンビニで決済を行う必要があります。 「ユーザーにコンビニでの決済を案内する画面が表示されること」と「コンビニ決済独自のテスト用コードが用意されていること」の2点以外は、他の遅延型決済と同じように処理することができます。 コードで実装する(Node.js編) コードでの実装の場合、Stripeが提供するREST APIをSDK経由で実行します。 この記事では、Node.jsを例に実装方法を紹介しますが、APIドキュメントではその他の言語やcUrl・Stripe CLIでの実行方法も確認できます。 基本的には、「[手動支払い用の請求書を顧客にメールで送付する]設定でサブスクリプションを作成するコード」に少し手を加える程度です。 const newSubscription = await stripe.subscriptions.create({ customer: 'cus_xxxx', items: [{ price: 'price_xxxx', quantity: 1 }], collection_method: 'send_invoice', days_until_due: 30, payment_settings: { payment_method_types: ['konbini', 'card'], }, }) payment_method_typesの指定を省略した場合、管理画面で有効化している決済方法を顧客が選べるようになります。 こちらの場合も、Dashboard操作と同じく、メールに記載されたリンクから、コンビニ決済を選択することで支払い方法を取得することができます。 サブスクリプションでコンビニ決済をサポートする際の注意点 「更新・契約と決済が同時期ではない」ケースが発生する コンビニ決済などでは、サブスクリプションを作成・更新したタイミングではまだ決済が完了していません。 そのため、サブスクリプション作成時の処理やイベントで、サービス提供を開始するシステムの場合は、決済が完了していない状態でユーザーがサービスを利用できる状態になります。 「発行された請求書やサブスクリプションが期限切れになったタイミングでクローズする」措置を取ることも可能ですが、もし決済が完了してから利用開始させたい場合は、Webhookを利用した実装への変更を検討しましょう 返金が必要な場合、銀行口座を顧客に登録してもらう必要がある クレジットカードでの決済の場合は、カード側に返金を行うことができます。しかしコンビニ決済の場合は、返金額を振り込むための銀行口座情報を顧客に入力してもらう必要があります。 口座情報の入力は、Stripeが顧客に送信するメールから行えますので、通常のDashboardからの返金操作を実行後、顧客の操作を待ちましょう。 Webhookでコンビニ決済のサイクルを処理する コンビニ決済など、顧客側で決済のためのアクションが必要なサブスクリプションでは、「決済が完了した時点から、サービスの提供を開始する」仕組みが必要になります。 そのため、Webhookのイベントを利用し、statusがactiveになったタイミングでアプリのDBを更新するなどの処理を行いましょう。 if (event.type === 'customer.subscription.updated') { const subscription = event.data.object; if (subscription.status === "active") { /** * サブスクリプションを開始することを、システムに伝える。 **/ } } また、次回以降の請求についても、コンビニ決済では顧客側のアクションが必要です。 そのため、もしCRMなどで顧客のフォローを行いたい場合などでは、以下のようにイベントを受け付けましょう。 if (['invoice.payment_action_required'].includes(event.type)) { /** * コンビニでの決済など、顧客側で対応が必要な請求が発生した際の処理を実装する。 * - UIやメールなどでユーザーに通知する * - CRMなどを経由して自社サポートチームにフォローを依頼する */ } Webhookの利用法についての詳細は、ドキュメント( https://stripe.com/docs/billing/subscriptions/webhooks )をご確認ください。 おわりに クレジットカード以外の決済方法を提供できるようにすることで、サービスによってはリーチできる顧客の幅が広がります。 Webhookイベントをベースとしたシステム体系にする必要はありますが、一度対応が完了できれば、今後も同様の決済方法が増えた場合のサポート工数を減らすことができます。 すでに本番環境で Stripe アカウントをご利用の場合、ダッシュボード上から審査手続きを開始することができます。 「設定」>「支払い方法」より、「有効にする」ボタンをクリックしてください。 [関連記事]コンビニ決済入門シリーズ [関連記事]コンビニ決済入門シリーズ Checkout / Elementsへの導入方法 Webhookでの処理方法 Connectを利用したマーケットプレイスへの導入方法
- 投稿日:2022-03-03T15:04:05+09:00
Stripe Elements / Checkoutを利用したコンビニ決済で、Webhookを利用した在庫管理・発送業務との連携を実装する
Stripeでは、カード決済の他にコンビニ決済についても顧客に提供できます。 ただし、カード決済とは決済のタイミングが異なるため、導入前にシステムの対応状況を確認する必要があります。 Dashboardで都度状況を確認することももちろん可能です。 ですが、StripeのWebhookを利用することで、手動のオペレーションを最小化できるシステムを最小化することができます。 また、Webhookを利用したアプリケーションを実装する以外にも、Zapierなどのタスク自動化ツールを活用することで、ノーコードで実現させることも可能です。 今回は、在庫管理や発送業務といった、決済が完了した後に執り行う業務への連携について、Stripe Webhookを利用する方法の例を紹介します。 なお、コンビニ決済の導入方法については、以下の記事をご覧ください。 コンビニ決済の導入前に確認が必要なこと まず、確認が必要な項目をおさらいしましょう。 重要な点は以下の3つです。 1: ElementsやCheckoutでの操作完了時に、まだ決済は完了していない 2: 数日後、未決済を理由に注文が自動キャンセルされることがある 3: 決済が完了したことは、payment_intent.succeededイベントで知ることができる このことから、以下の要件を満たしているかを確認する必要があることがわかります。 PaymentIntentまたはCheckout セッション作成時に、在庫を確保する 「Elements / Checkoutの操作完了時」ではなく、payment_intent.succeededイベント受信時に発送業務を行う 未決済を理由に注文がキャンセルされた場合、確保した在庫を解放する 上記の3点について、簡単なJavaScriptアプリケーションを例に実装のアイディアを深めましょう。 コンビニ決済に対応したWebhookイベント処理を実装する ここからは前述の3要件を満たす実装の例を紹介します。 PaymentIntent / Checkout セッションを作成し、在庫を確保する まずは注文処理に入るため、PaymentIntentまたはCheckout セッションを作成しましょう。 例1: Payment Intentを作成するサンプルコード /** * 在庫の確認と確保の処理を追加する。 **/ try { /** * PaymentIntentを作成する。 * payment_method_optionsを使って、支払い期限や明細表示などをカスタムする。 */ const paymentIntent = await stripe.paymentIntents.create({ amount: 2000, currency: 'jpy', payment_method_types: ['konbini', 'card'], payment_method_options: { konbini: { product_description: "Tシャツ", expires_after_days: 1 } }, }); /** * 注文管理のDBがある場合、 * PaymentIntentのIDと在庫を確保した商品のデータを保存する。 */ return paymentIntent; } catch (e) { /** * PaymentIntentの作成などに失敗した場合、 * 確保した在庫を解放する **/ } 例2: Checkoutセッションを作成するサンプルコード Checkoutセッションの場合も、PaymentIntent作成処理部分を以下のように変更すればOKです。 PaymentIntentの場合と異なり、Checkoutセッションの場合は、Product / PriceのデータをStripeに登録する必要があります。 その代わりに合計金額の計算を行う必要がなくなります。 また、shipping_optionsを利用して、配送方法・料金の選択画面を表示できるようにしましょう。 /** * 在庫の確認と確保の処理を追加する。 **/ try { const session = await stripe.checkout.sessions.create({ line_items: [{ price: 'price_xxxx', quantity: 1 }], /** * The URL to which Stripe should send customers when payment or setup is complete. **/ success_url: 'http://localhost:3001/success', /** * The URL the customer will be directed to if they decide to cancel payment and return to your website. **/ cancel_url:'http://localhost:3001/cancel', mode: 'payment', payment_method_types: ['card', 'konbini'], customer_creation: 'if_required', shipping_address_collection:{ allowed_countries: ["JP"] }, shipping_options: [{ shipping_rate: 'shr_xxxxx' }] }); /** * 注文管理のDBがある場合、 * CheckoutセッションのIDと在庫を確保した商品のデータを保存する。 */ return session; } catch (e) { /** * Checkoutセッションの作成などに失敗した場合、 * 確保した在庫を解放する **/ } Checkoutセッションを利用する場合、カートの中身のデータはcheckout.sessions.listLineItemsAPIから取得できます。 こちらを利用する方法も可能ですので、興味がある方はぜひお試しください。 クレジットカードやコンビニ決済の注文を処理するWebhookを作成する いずれの決済方法でも、処理が完了した時にpayment_intent.succeededイベントが発火します。 ただし発火のタイミングが決済方法によって変わることになるため、顧客のために在庫を確保している期間が変動することにご注意ください。 switch (event.type) { case 'payment_intent.succeeded': { /** * オブジェクトの中身は、APIドキュメントをご確認ください。 * @see https://stripe.com/docs/api/payment_intents/object **/ const paymentIntent = event.data.object; /** * 注文した商品のデータをDBなどに保存している場合、 * PaymentIntentのIDを利用してデータを取得する処理を入れる */ const orderItems = await getOrderedItemByPaymentIntentId(paymentIntent.id) /** * Checkoutなどで顧客が入力した配送先住所を取得する */ const shippingData = paymentIntent.shipping; /** * 配送先情報や注文した商品・顧客データを利用し、 * 発送業務などを実行するAPIまたは処理を呼び出す。 */ break; } default: break; } 受信したPaymentIntentのデータには、以下のデータなどが含まれています。 顧客とPaymentIntentのID 注文金額・決済金額 PaymentIntent作成時に登録したmetadata 顧客が選択した配送先情報 領収書送信のためのメールアドレス より詳細なデータを確認したい場合は、APIドキュメントをご覧ください。 また、発送処理が失敗した時に備えて、Amazon CloudWatchやSentryなどを設定することをお勧めします。 これにより、Slackまたはメールで問題発生に気づきやすくなります。 決済が未完了だったケースを処理するWebhookを作成する 有効期限までに決済が完了されなかった場合、payment_intent.payment_failedイベントが発火します。また、Checkoutでカゴ落ちが発生し、セッションが有効期限切れになった場合にはpayment_intent.canceledイベントが発火します。 そのため、確保した在庫を解放するには、この2イベントを受け付ける必要があります。 switch (event.type) { // 決済成功時の処理 case 'payment_intent.succeeded': { const paymentIntent = event.data.object; ... break; } // 注文未完了(キャンセル)時の処理 case 'payment_intent.canceled': case 'payment_intent.payment_failed': { const paymentIntent = event.data.object; /** * PaymentIntentのIDを利用して、在庫の数などを元に戻す処理を実行する */ break; } default: break; } また、もし在庫を確保したままで問題がないケースであれば、PaymentLinksとクーポンを利用して、ユーザーに特別オファーを提供するメールを送信するなども考えることができます。 const promotionCode = await stripe.promotionCodes.create({ customer: dataObject.customer, coupon: 'COUPON_ID' }) const link = await stripe.paymentLinks.create({ line_items: [...], allow_promotion_codes: true, }) /** * Payment LinksのURLとPromotionCodeを送信するメールを送付する **/ おわりに 今回紹介した手法は、現在ベータ版として提供中の銀行振り込みのように、今後Stripeが新しい日本固有の決済方法をサポートした場合などでも応用できます。 また、3Dセキュアなどの本人確認に対応する場合にも対応が必要となるケースがありますので、この機会にシステムの確認をしてみてはいかがでしょうか? [関連記事]コンビニ決済入門シリーズ Checkout / Elementsへの導入方法 Billing(サブスクリプション)への導入方法 Connectを利用したマーケットプレイスへの導入方法
- 投稿日:2022-03-03T15:01:57+09:00
Stripe Checkout / Elementsで、コンビニ決済のサポートを開始する3つの方法
Stripeを利用することで、顧客は料金の支払いをコンビニで行えます。また、Stripeユーザーは、コンビニでの決済処理結果を、Stripe DashboardやWebhookを通して受け取ることができます。 コンビニ決済を有効化することで、これまでリーチすることが困難だった「クレジットカードを持たない顧客」にリーチすることが可能です。また、顧客も公共交通機関での移動中や、信頼性の低いネットワークを利用している状況など、「クレジットカードを取り出して入力する」操作がやりにくい場面でも、安心して注文ができるようになります。 この記事では、Stripeを利用した決済で、コンビニ決済をサポートする方法について、「Checkout」「PaymentElements」そして「Elements(PaymentElements不使用)」の3種類を紹介します。 コンビニ決済をサポートする3つの方法 この記事では、Node.jsとReactを例に実装方法を紹介します。phpやRubyなど、その他の言語やフレームワークでの実装については、APIドキュメント(https://stripe.com/docs/api) をご確認ください。記事内で紹介しているメソッド・関数を、それぞれの言語で実行する方法を見ることができます。 1: Checkoutを利用して、決済処理を行なっている場合 Stripe Checkoutを利用して決済画面を提供している場合、組み込みはとても簡単です。 決済方法を、コードまたはDashboardから管理ができますので、お使いの管理方法に合わせて変更を行いましょう。 コード側で決済方法を管理する場合 コードで決済方法を指定している場合、以下の例のように、payment_method_typesにkonbiniを追加します。 await stripe.checkout.sessions.create({ line_items: [{ price: 'price_xxxxxx', quantity: 1 }], success_url: 'http://localhost:3000/success', cancel_url:'http://localhost:3000/cancel', mode: 'payment', payment_method_types: ['card', 'konbini'], customer_creation: 'if_required' }) Dashboardで決済方法を管理する場合 payment_method_typesを削除して、ダッシュボードから決済方法( https://stripe.com/docs/payments/dashboard-payment-methods )を設定します。 await stripe.checkout.sessions.create({ line_items: [{ price: 'price_xxxxxx', quantity: 1 }], success_url: 'http://localhost:3000/success', cancel_url:'http://localhost:3000/cancel', mode: 'payment', - payment_method_types: ['card'], customer_creation: 'if_required' }) 表示を確認する 変更が完了すると、Checkoutの支払い方法に「コンビニ決済」が表示されます。 2: ElementsのPaymentElementを利用している場合 PaymentElementを利用して決済フォームを実装している場合についても、ほとんど設定の変更が必要ありません。 PaymentIntentを作成する際に、payment_method_typesを指定している場合のみ、konbiniを追加しましょう。 const paymentIntent = await stripe.paymentIntents.create({ amount: 2000, currency: 'jpy', payment_method_types: ['konbini', 'card'], }) 以下の例のように、payment_method_options['konbini']に追加情報を登録することもできます。 const paymentIntent = await stripe.paymentIntents.create({ amount: 2000, currency: 'jpy', payment_method_types: ['konbini', 'card'], payment_method_options: { konbini: { product_description: "Tシャツ", expires_after_days: 3, tax_amount: 200 } } }) あとはPaymentIntentのClient SecretをStripe.jsに渡すだけで、PaymentElementがコンビニ決済を選択できるように表示を変更します。 カードなどの処理と同様、confirmPaymentを実行すると、PaymentIntentがconfirmされます。 3: ElementsでPayment Elementを利用して「いない」場合 Elementsを利用する場合、基本的にはPaymentElementを利用することをお勧めします。 しかしサイトデザインの要件を満たすために、PaymentElement以外のElementを利用する必要があるケースでは、上記のPaymentElementを利用した実装が行えません。 Payment Elementを利用せずに実装する場合、フロントエンドにも追加の実装が必要です。 最も簡単な方法は、「コンビニ決済を処理するためのformを、新しく用意すること」です。 まず、「氏名・メールアドレス・電話番号」を入力するフォームを用意します。 const KonbiniForm: FC = () => { const [name, setName] = useState("") const [email, setEmail] = useState('') const [phone, setPhone] = useState('') return ( <form onSubmit={async (e)=>{ e.preventDefault() }> <div> <label>電話番号</label> <input type='tel' value={phone} onChange={e=>setPhone(e.target.value)} /> </div> <div> <label>名前</label> <input type='text' value={name} onChange={e=>setName(e.target.value)} /> </div> <div> <label>メールアドレス</label> <input type='email' value={email} onChange={e=>setEmail(e.target.value)} /> </div> <button type='submit' disabled={status === 'inprogress'}>注文する</button> </form> ) } その後、PaymentIntentを作成するAPI呼び出しと、コンビニ決済を処理するメソッドを呼び出す実装をsubmitイベントに設定します。 const KonbiniForm: FC = () => { const stripe = useStripe() const [name, setName] = useState("") const [email, setEmail] = useState('') const [phone, setPhone] = useState('') return ( <form onSubmit={async (e)=>{ e.preventDefault() try { if (!stripe) return; const intent = await fetch('/api/payment_intent', { method: 'post' }).then(data=>data.json()) const result = await stripe.confirmKonbiniPayment(intent.client_secret, { payment_method: { billing_details: { name, email, }, }, payment_method_options: { konbini: { confirmation_number: phone, }, }, } ) console.log(result) } catch(e) { console.log(e) } }}> ... confirmKonbiniPaymentが成功すると、こちらもコンビニでの支払い情報がモーダル表示されます。 もし独自のUIで情報を表示させたい場合は、以下のように引数を追加します。 const result = await stripe.confirmKonbiniPayment(intent.client_secret, { payment_method: { billing_details: { name, email, }, }, payment_method_options: { konbini: { confirmation_number: phone, }, }, }, { handleActions: false } ) handleActions: falseを設定すると、モーダルが表示されなくなります。 コンビニでの支払いのために必要な情報は、result.paymentIntent.next_actionから取得することができます。 この中には、以下の情報が含まれています。 支払い期日(konbini_display_details.expires_at) Stripeが用意する支払い方法ページのURL(konbini_display_details.hosted_voucher_url) コンビニチェーンごとの入力情報(確認番号と支払い番号) (konbini_display_details[familymart|lawson|ministop|seicomart]) コンビニのロゴ・アイコン画像などは含まれません。実装時に自分で用意する必要がありますので、ご注意ください。 コンビニ決済をサポートする際の注意点 利用開始前に審査を受ける必要があります コンビニ決済を有効化するには、事前に審査を受ける必要があります。 この審査は最低でも3週間程度かかりますので、なるべく早いタイミングで申請を行うようにしましょう。 また審査では、「特定商取引法に基づく表記がされているか」や「禁止業種に該当しないか」・「Stripeアカウントの本番環境申請が完了しているか」などが確認されます。 情報が不足している場合には、メールにてご連絡いたしますが、審査の完了までに追加の時間が発生しますのでご注意ください。 注文完了時点では、決済が完了していません コンビニ決済での注文の場合、実際に顧客が代金を支払うまでに時間差があります。 そのため、カード決済のみ対応していたサービスなどでは、「決済が完了したことを確認できるまで、発送業務等の後続処理を行わない」実装になっているかを確認しましょう。 もし決済完了を確認せずに後続の業務を行なった場合、実際には決済が完了できていない(コンビニでの支払いが実行されなかった)場合にも商品を発送してしまうなどの問題が発生します。 Checkoutを利用している場合 Checkoutを利用した決済では、まずcheckout.session.completedイベントを受け付けます。Webhookイベント内の"payment_status"をチェックし、カード決済などで支払い済みの場合(payment_status=paid)ならば後続業務を実行します。 コンビニ決済など、支払いが未完了の場合は、payment_status=unpaidですので、在庫の確保を続けるだけにするなど、決済の完了を待つシステムにする必要があります。 顧客の決済が完了した場合には、追加でcheckout.session.async_payment_succeededイベントが発生します。こちらのイベントを受け付けた場合にはじめて発送等の業務を開始させましょう。 Elements(PaymentIntent)を利用している場合 Elements(PaymentIntent)を利用している場合、カード決済でもコンビニ決済でもpayment_intent.succeededイベントの発火で後続業務を開始できます。 Webhookを利用した期限切れの処理が必要になるケースがあります コンビニ決済では、設定した期限内に支払いが行われないケースも発生します。その場合、確保している在庫を開放するなどの操作が必要になります。 この操作を実装するには、Webhookで以下のイベントを受け付ける必要があります。 Checkoutを利用したケース:checkout.session.async_payment_failed Elements(PaymentIntent)を利用したケース: payment_intent.payment_failed また、支払いが完了していない顧客に、メールを送信する仕組みも用意されています。 [設定 > メール]から設定することができますので、こちらも合わせてご利用ください。 Webhookを利用するコードサンプルなどは、以下の記事をご覧ください。 手数料の最低金額が設定されています コンビニ決済を利用する場合、決済手数料は最低でも120円が発生します。 基本の決済手数料が3.6%ですので、おおよそ3,300円以下の商品については、決済手数料が通常よりも高くなります。 返金に手数料がかかります 顧客からの要求などで返金を行う場合にも、¥250 + 10% 消費税 (計 ¥ 275)の手数料が発生します。 ご注意ください。 おわりに コンビニ決済をサポートすることで、クレジットカードを持たない顧客でもサービスを利用できるようになります。 すでに本番環境で Stripe アカウントをご利用の場合、ダッシュボード上から審査手続きを開始することができます。 「設定」>「支払い方法」より、「有効にする」ボタンをクリックして、サポートを開始しましょう。 [関連記事]コンビニ決済入門シリーズ Webhookでの処理方法 Billing(サブスクリプション)への導入方法 Connectを利用したマーケットプレイスへの導入方法
- 投稿日:2022-03-03T14:37:30+09:00
function()関数をアロー関数に書き換えよう
主旨 学習メモの記事にコメントを頂き、その中で、、、 「function()使ったり、letで変数宣言してるけど、もっと簡単な書き方あるよ〜」 と教えていただき、その内容を自分なりにまとめてみたもの やること 1、変数定義letの省略 2、アロー関数への書き換え 今回の問題となる僕の元コードがこちら、、、 0padding.js const twoDigital = function(num) { let digit = String(num).padStart(2,0); return digit; } ↑をスッキリした記述にすると↓になる。 0padding.js const twoDigital = num => String(num).padStart(2, '0'); (ちなみにこれは2桁のゼロパディング関数の宣言と定義に関するコード numを引数) 1,letの変数宣言無くす そもそも、変数を表示させるほどの複雑さではないし、 padStartしたString(num)をそのまま返せば良い。 ので、letで宣言した「digit」に関する記述を削除 js.0padding.js const twoDigital = function(num) { return String(num).padStart(2,0); } 2,アロー関数への書き換え この記事の書き替え手順により、行ってみる 書き替え前(constの部分は省略) function(num) { return String(num).padStart(2,0); } 1、functionを削除 引数と中身定義する中カッコの間に「=>」を入れる (num) => { return String(num).padStart(2,0); } 2、中括弧削除、returnは記述省略可能 (改行も解消しました) (num) => String(num).padStart(2,0); 3、引数の括弧を削除 → 完成!! num => String(num).padStart(2,0); 感想 現在は基本的にアロー関数で記述できるものはアロー関数を使うのが主流のようなので、慣れたい。 書き直しを行って、引数の理解もちょっと深まったような気がする。 わからない事ばかりだけど、ちょっとずつ頑張ろう。 今回コメントでアドバイスいただいた、、、、 @shiracamus様、@ris292様、@juner様 ありがとうございます。 参考URL アロー関数の説明がわかりやすい(とっつきやすい) function関数からの書き替え
- 投稿日:2022-03-03T11:28:00+09:00
Stripe Checkoutで「カートの中身」または「実際に注文された商品」の種類と数を取得する方法
Stripe Checkoutを利用することで、手軽に安全なオンライン注文ページを顧客に提供することができます。 また、Checkoutの設定を変更することで、「カートの中の商品の数量を変更」したり、「アップセルの提案」を行うことも可能です。 これにより、顧客は注文の直前にカートの中身を再調整することができ、前のページに戻るなどの余計な手間を省くことができます。 カートの中身を取得する方法 「ブラウザがエラーでダウンした・移動中で一時的にオフラインになった」などの理由で、Checkoutのセッションが一時的に途切れるケースが存在します。 この場合、顧客はCheckoutページで変更した後の数量で、注文を再開したいと考えます。 その場合、システム側では、Stripe APIを利用し、カートの中身を取得する必要があります。 const items = await stripe.checkout.sessions.listLineItems(sessionId); // cs_xxxx このAPIで取得できるデータの例はこちらです。 料金データや数量などが含まれていることが確認できます。 [{ id: 'li_xxxx', object: 'item', amount_subtotal: 1000, amount_total: 1000, currency: 'jpy', description: 'Starter plan', price: { id: 'price_xxxx', object: 'price', active: true, billing_scheme: 'per_unit', created: 1639111716, currency: 'jpy', livemode: false, lookup_key: null, metadata: {}, nickname: null, product: 'prod_xxxx', recurring: null, tax_behavior: 'inclusive', tiers_mode: null, transform_quantity: null, type: 'one_time', unit_amount: 1000, unit_amount_decimal: '1000' }, quantity: 1 }] 顧客に同じCheckoutセッションのURLを再提供したい場合 何かしらの理由で、顧客がCheckoutセッションから離脱してしまった場合、同じURLを再度ユーザーに提供することもできます。 const session = await stripe.checkout.sessions.retrieve(sessionId) // cs_xxxx if (session.url || session.status !== 'expired') { return { url: session.url, id: session.id, }; } // 有効期限が切れているので、セッション再作成 セッションの有効期限が切れていなければ、retrieveAPIのレスポンスにURLが含まれます。 これを利用して、中断したカートセッションを再開しましょう。 Webhookなどで、実際に注文された商品データを取得する checkout.session.completedやcheckout.session.async_payment_succeededイベントなどを利用し、注文が完了した後の業務を自動化することもできます。 app.post('/webhook', express.raw({type: 'application/json'}), async (request, response) => { let event = request.body; /** * Webhookの署名検証処理がここに入ります * @see https://stripe.com/docs/webhooks/signatures#verify-official-libraries **/ const data = event.data.object; if ( event.type !== 'checkout.session.completed' && event.type !== 'checkout.session.async_payment_succeeded' ) { return response.sendStatus(200); } /** * 支払いが完全に完了している場合のみ処理する **/ if (data.payment_status === 'paid') { const { items } = await stripe.checkout.sessions.listLineItems(data.id); /** * カートの中身の情報を利用して、発送業務などのシステムを呼び出す **/ } return response.sendStatus(200); }); 合計金額や配送料データなどはイベントで受信するデータに含まれています。 が、カートの中身については別途APIで取得する必要があることにご注意ください。 応用アイディア: 期限切れの場合のみ、セッションを新しく作成する これまでの内容を応用すると、以下のような処理が作れます。 セッションが有効ならば、そのままURLを返して再度Checkoutページに移動させる セッションが期限切れの場合、中断されたカートの中身や設定をできるだけ再現させたセッションを再作成する // TypeScript const recreateCheckoutSession = async (sessionId: string): Promise<{ url: string; id: string; }> => { const session = await stripe.checkout.sessions.retrieve(sessionId); if (session.url && session.status !== 'expired') { return { url: session.url, id: session.id, }; } const items = await stripe.checkout.sessions.listLineItems(sessionId); const params: Stripe.Checkout.SessionCreateParams = { line_items: items.data.map(data => { if (!data.price) return null; return { price: data.price.id, quantity: data.quantity || 1, }; }).filter(Boolean) as any, cancel_url: session.cancel_url, success_url: session.success_url, mode: session.mode, expires_at: session.expires_at, }; if (session.customer) { params.customer = session.customer as string; } else if (session.customer_creation) { params.customer_creation = session.customer_creation; } const newSession = await stripe.checkout.sessions.create(params); return { newSession, items, }; }; このコードはすべての引数・料金体系をサポートしていません。 そのため、このままではセッションの再開がうまく動かないケースもあることにご注意ください。 [PR] Stripe開発者向け情報をQiitaにて配信中! [Stripe Updates]:開発者向けStripeアップデート紹介・解説 ユースケース別のStripe製品や実装サンプルの紹介 Stripeと外部サービス・OSSとの連携方法やTipsの紹介 初心者向けのチュートリアル(予定) など、Stripeを利用してオンラインビジネスを始める方法について週に2〜3本ペースで更新中です。 -> Stripe Organizationsをフォローして最新情報をQiitaで受け取る
- 投稿日:2022-03-03T09:56:05+09:00
TypeScriptでモーダル(ポップアップ画像)を実装する方法
巷でよく見るイメージモーダル(画像表示→非表示)をTypeScriptで作ってみた LPやサイトコンテンツでもモーダル機能はよく利用されています。 JavaScriptでの実装例はQiitaを含めネットでも多く記事がありますが、TypeScriptでの実装例は少なかったのでまとめてみました。 機能としては以下の内容になります。 ページ読み込み時にモーダル画像が表示※今回はpixabayからフリー素材を転用 メインコンテンツが透けて見える 画像 or グレーアウトした背景部分をクリックすると画像が非表示になる 環境構築 今回はyarnとViteを使って構築しております。 Viteに関しては詳細は割愛しますが開発効率面でざっくり以下のメリットがあるようです。 Goで書かれたesbuildを利用して依存関係の事前ビルドを行う(JavaScript ベースよりも 10 倍から 100 倍高速) Native ESM(ビルド後にもES Modulesが利用される)を利用することでブラウザ側でimport/exportを解析できるためバンドルが不要になる。 お手元でTypeScriptが動く環境であれば特に環境構築部分は飛ばして頂いて問題ありません。 npmを確認 # node.jsのバージョンを確認 node -v # npmのバージョンを確認 npm -v npm 経由でyarnをインストール # npm 経由でyarnをインストール npm install -g yarn # yarnのバージョンを確認 yarn -v # yarnの初期設定 yarn init ViteでTypeScriptプロジェクト作成 yarn create vite TypeScriptを選択 ? Project name: › hello-vite # (1)プロジェクト名を入力 ? Select a framework: › - Use arrow-keys. Return to submit. ❯ vanilla # (2)フレームワークを選択(vanilla = フレームワークを使わない) vue react preact lit-element svelte ? Select a variant: › - Use arrow-keys. Return to submit. vanilla ❯ vanilla-ts # (3)TypeScriptを選択 nodeパッケージをインストール yarn install モーダル部分 実装部分は以下の通りです。 index.htmlに予めモーダルを画像を用意(visibility: hidden;で非表示) ユーザーのクリックイベントでvisibility: visible;をつけて表示する main.ts import './style.css' // ページが読み込まれたタイミングで実行 window.onload = () => { const popup = document.getElementById('popup') as HTMLDivElement | null; // モーダル画像表示 popup!.classList.add('show'); // 各要素取得 const blackBg = document.getElementById('modalBackground') as HTMLDivElement | null; const modalWindow = document.getElementById('modalWindow') as HTMLDivElement | null; // 各要素にイベントを設定 closePopUp(blackBg); closePopUp(modalWindow); // クリック発生でモーダル画像を非表示にする function closePopUp(elem: HTMLElement | null) { if (!elem) return; elem.addEventListener('click', () => { popup?.classList.remove('show'); }); } } index.html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <link rel="icon" type="image/svg+xml" href="favicon.svg" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Vite App</title> </head> <body> <div id="app"> <h1>にゃんこ画像のモーダルサンプル</h1> <p>にゃんこ。にゃんこ。にゃんこ。にゃんこ。にゃんこ。にゃんこ。</p> <p>にゃんこ。にゃんこ。にゃんこ。にゃんこ。にゃんこ。にゃんこ。</p> <p>にゃんこ。にゃんこ。にゃんこ。にゃんこ。にゃんこ。にゃんこ。</p> </div> </div> <!-- モーダル画像部分はじまり --> <div class="popup" id="popup"> <div class="modalWindow" id="modalWindow"> <!-- 任意のモーダル画像を設定 --> <img src="https://cdn.pixabay.com/photo/2014/04/13/20/49/cat-323262_960_720.jpg" alt="フリー素材の猫画像"> </div> <div class="modalWrapper" id="modalBackground"></div> </div> <!-- モーダル画像部分おわり --> <script type="module" src="/src/main.ts"></script> </body> </html> style.css .popup { position: fixed; left: 0; top: 0; width: 100%; height: 100%; z-index: 9999; opacity: 0; visibility: hidden; transition: .6s; } /* クリックイベントでこのスタイルを取り除く */ .popup.show { opacity: 1; visibility: visible; } .modalWrapper { width: 100%; height: 100%; top: 0; right: 0; bottom: 0; left: 0; background: rgba(0, 0, 0, .7); position: fixed; z-index: 5; cursor: pointer; } .modalWindow { width: 750px; height: 460px; z-index: 20; position: absolute; top: 50%; left: 50%; transform : translate(-50%, -50%); cursor: pointer; } .modalWindow img { width: 100%; height: auto; } ローカルサーバー起動で確認 yarn dev yarn vite 参考 主に環境構築で参考にしました。
- 投稿日:2022-03-03T08:58:34+09:00
JavaScript: クロージャ [アウトプット全くしてこなかったのでアウトプットする003]
クロージャについてあまり使うことがなく知識が曖昧なのでアウトプットしました クロージャは、組み合わされた(囲まれた)関数と、その周囲の状態(レキシカル環境)への参照の組み合わせです。言い換えれば、クロージャは内側の関数から外側の関数スコープへのアクセスを提供します。JavaScript では、関数が作成されるたびにクロージャが作成されます。 By MDN(久しぶりにサイト見に行ったら俺の知っているMDNじゃなくなっていた...) いつも通りわからないことをわからないことで説明され余計わからなくなるパターンです。 ただ「わかったときにこの説明がしっくりくる...」というパターンでもあります。 まずレキシカルスコープの理解が必要でした。 レキシカルスコープ function init() { var name = 'Mozilla'; // name は、init が作成するローカル変数 function displayName() { // displayName() は内部に閉じた関数 alert(name); // 親関数で宣言された変数を使用 } displayName(); } init(); 関数内で指定された変数を探すときに inner => outer => outerのように探していきます。 例) 鍵(変数)をなくしたときにポケット(inner)から探します。次に机の上(outer)、次に部屋全体(outer)、次に家全体(outer)、次に家の外(outer).......... 上記のコードでは、displayName関数の中で関数内にname変数があるかまず確認します。なかったので一段階上のinit関数の中からnameを探します。ありました。 init()内の変数nameはinit()が実行されると消えて無くなります。 通常、関数呼び出しが終わった後、すべての変数のとともに、レキシカル環境はメモリから削除されます。これはそこへの参照が存在しないためです。他のJavaScriptオブジェクトと同様に、到達可能な間だけメモリに保持されます。 しかし、関数の終了後も依然として到達可能なネストされた関数がある場合、それはレキシカル環境への参照である [[Environment]] プロパティを持ちます。 この場合、レキシカル環境は関数の完了後も依然として到達可能なので、存在し続けます。 変数nameをどうにかして生かす方法があります。 以下のクロージャでは 引用文のこれはそこへの参照が存在しないためです この言葉が肝になります。 クロージャ function makeAdder(x) { return function(y) { return x + y; }; } var add5 = makeAdder(5); var add10 = makeAdder(10); console.log(add5(2)); // 7 と表示される console.log(add10(2)); // 12 と表示される 内部のレキシカル環境は外部のものへの 外部 参照を持っています。 コードが変数にアクセスしたいとき、最初に内部のレキシカル環境を探します。その次に外側を探し、チェーンの最後になるまで繰り返します。 クロージャではこのレキシカルスコープを使用して値を保持し続けるということをしています。 なので以下のように値を保持してcountを増減させるといったことができます。 function makeCounter() { let count = 0; return function() { return count++; }; } let counter = makeCounter(); alert( counter() ); // 0 alert( counter() ); // 1 alert( counter() ); // 2 Reactのhooksとかこういった機能で成り立たせているのか?など思ったりしました。 実際に自分がクロージャを使うことを考えたときにカプセル化になるからいいんじゃないかと思ったのですが、デバッグできないというデメリットがあるらしいです。 以下のクロージャのクイズが面白かったので見てみてください。 参考 強くなりたい!!!!!!!
- 投稿日:2022-03-03T08:40:13+09:00
規則性の低い数列表から無検索逆引き
バラバラの数列の数からインデックスを得るとき、高級言語(死語?)では検索機能(table.index(N)など)がありますが、低級言語では検索なしにインデックスを得たい気分になります。(ここでの検索とは比較を繰り返しす処理を意味します) テーブル中の数が重複して出てこないとき、剰余を使うと検索なしにインデックスを得られる場合があります。 例(除数は45) >>> table = [57183, 26302, 12855, 61489, 21256, 14532, 24168, 37683, 18386, 35798, 7211] >>> [n % 45 for n in table] [33, 22, 30, 19, 16, 42, 3, 18, 26, 23, 11] >>> [[-1, -1, -1, 6, -1, -1, -1, -1, -1, -1, -1, 10, -1, -1, -1, -1, 4, -1, 7, 3, -1, -1, 1, 9, -1, -1, 8, -1, -1, -1, 2, -1, -1, 0, -1, -1, -1, -1, -1, -1, -1, -1, 5, -1, -1, -1] [n % 45] for n in table] [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] 目的の数列に対する除数を得られれば検索せずに済むことになります。 除数の検索と逆引き表の生成プログラム コマンドライン引数に数列を渡すと、除数の検索と逆引き表の生成を行っています。 オプションで以下のことができます。 逆引きでの無効を示す値の変更 (-d N) 除数の上限を設定 (-l N) findivisor.py #!/usr/bin/env python3 def find_divisor(table, limit=None): "単射となる除数を探す" ntab = len(table) if not ntab: return 0 if len(set(table)) != ntab: raise Exception('表中の数に重複があります') btab = ntab.bit_length() if limit is None: limit = max(table) divisor = (1 << (btab - 1)) + 1 injective = False while divisor <= limit and not injective: remain = tuple(n % divisor for n in table) used = {(n % divisor) for n in table} injective = (len(used) == ntab) if injective: break divisor += 2 return (injective and divisor or 0) def make_index(divisor, table, defval): "逆引き表を生成する" remain = tuple(n % divisor for n in table) index = [defval] * (divisor + 1) for n in range(len(table)): index[table[n] % divisor] = n return tuple(index) # コマンドライン処理 if __name__ == '__main__': import sys import random import argparse def format_list(data, *, width=16, prefix='', datafmt='%s', msgfmt='%#s', separator=',', space=1, suffix='\n'): fmtdata = [datafmt % d for d in data] msgfmt = msgfmt.replace('#', ('%d' % max([len(s) for s in fmtdata]))) msglist = [(msgfmt % d) + separator for d in fmtdata] return (suffix.join([prefix + (' ' * space).join(msglist[i:min(i+width, len(msglist))]) for i in range(0, len(msglist), width)]) + suffix) parser = argparse.ArgumentParser(description='剰余による数列の逆引き表生成支援道具') parser.add_argument('N', type=int, nargs='+', help='数列の数 (-1 を一つだけ指定すると乱数によるテスト)') parser.add_argument('-d', '--defval', metavar='N', help='表の穴埋め値を指定します (既定:表の大きさ)') parser.add_argument('-l', '--limit', metavar='N', help='除数の上限を指定します (既定:表の最大値)') args = parser.parse_args() table = args.N if len(table) == 1 and table[0] == -1: table = [] for n in range(random.randint(10, 50)): r = random.randint(0, 65535) if r not in table: table.append(r) limit = None if args.limit is None else int(args.limit) defval = len(table) if args.defval is None else int(args.defval) divisor = find_divisor(table, limit) if divisor <= 0: print('除数を見つけられませんでした') sys.exit(2) index = make_index(divisor, table, defval) print('除数:', divisor) print('逆引き:\n' + format_list(index, width=8, prefix=' '*4)[:-2]) print(('元の表: %d 個\n' % len(table)) + format_list(table, width=8, prefix=' '*4)[:-2]) 実行結果(逆引きの8は無効を示す) $ python3 findivisor.py 1 3 5 7 11 13 17 19 除数: 11 逆引き: 4, 0, 5, 1, 8, 2, 6, 3, 7, 8, 8, 8 元の表: 8 個 1, 3, 5, 7, 11, 13, 17, 19 テスト:数列として {-1} を与えると表は乱数生成になります。 実行結果 $ python3 findivisor.py -1 -d -1 除数: 45 逆引き: -1, -1, -1, 6, -1, -1, -1, -1, -1, -1, -1, 10, -1, -1, -1, -1, 4, -1, 7, 3, -1, -1, 1, 9, -1, -1, 8, -1, -1, -1, 2, -1, -1, 0, -1, -1, -1, -1, -1, -1, -1, -1, 5, -1, -1, -1 元の表: 11 個 57183, 26302, 12855, 61489, 21256, 14532, 24168, 37683, 18386, 35798, 7211
- 投稿日:2022-03-03T08:40:13+09:00
規則性の低い数列表に対する無検索逆引き
バラバラの数の数列からインデックスを得るとき、高級言語(死語?)では検索機能(table.index(N)など)がありますが、低級言語では検索なしにインデックスを得たい気分になります。(ここでの検索とは比較を繰り返しす処理を意味します) テーブル中の数が重複して出てこないとき、剰余を使うと検索なしにインデックスを得られる場合があります。 例(除数は45) >>> table = [57183, 26302, 12855, 61489, 21256, 14532, 24168, 37683, 18386, 35798, 7211] >>> [n % 45 for n in table] [33, 22, 30, 19, 16, 42, 3, 18, 26, 23, 11] >>> [[-1, -1, -1, 6, -1, -1, -1, -1, -1, -1, -1, 10, -1, -1, -1, -1, 4, -1, 7, 3, -1, -1, 1, 9, -1, -1, 8, -1, -1, -1, 2, -1, -1, 0, -1, -1, -1, -1, -1, -1, -1, -1, 5, -1, -1, -1] [n % 45] for n in table] [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] 例では、table の値を除数 45 で剰余したリストも中の数が重複して出てこない状態(単射)になっています。 目的の数列に対する剰余が単射となる除数を得られれば、逆引き表を使うことで検索不要にできます。 求めたい除数は、逆引き表の大きさになるので 初期値: 数列の大きさを奇数化 (len(table) | 1) 増加値: 2 で検索します。 除数の検索と逆引き表の生成プログラム コマンドライン引数に数列を渡すと、除数の検索と逆引き表の生成を行っています。 オプションで以下のことができます。 逆引きでの無効を示す値の変更 (-d N) 除数の上限を設定 (-l N) findivisor.py #!/usr/bin/env python3 def find_divisor(table, limit=None): "単射となる除数を探す" ntab = len(table) if not ntab: return 0 if len(set(table)) != ntab: raise Exception('表中の数に重複があります') if limit is None: limit = max(table) divisor = ntab | 1 injective = False while divisor <= limit and not injective: remain = tuple(n % divisor for n in table) used = {(n % divisor) for n in table} injective = (len(used) == ntab) if injective: break divisor += 2 return (injective and divisor or 0) def make_index(divisor, table, defval): "逆引き表を生成する" remain = tuple(n % divisor for n in table) index = [defval] * (divisor + 1) for n in range(len(table)): index[table[n] % divisor] = n return tuple(index) # コマンドライン処理 if __name__ == '__main__': import sys import random import argparse def format_list(data, *, width=16, prefix='', datafmt='%s', msgfmt='%#s', separator=',', space=1, suffix='\n'): fmtdata = [datafmt % d for d in data] msgfmt = msgfmt.replace('#', ('%d' % max([len(s) for s in fmtdata]))) msglist = [(msgfmt % d) + separator for d in fmtdata] return (suffix.join([prefix + (' ' * space).join(msglist[i:min(i+width, len(msglist))]) for i in range(0, len(msglist), width)]) + suffix) parser = argparse.ArgumentParser(description='剰余による数列の逆引き表生成支援道具') parser.add_argument('N', type=int, nargs='+', help='数列の数 (-1 を一つだけ指定すると乱数によるテスト)') parser.add_argument('-d', '--defval', metavar='N', help='表の穴埋め値を指定します (既定:表の大きさ)') parser.add_argument('-l', '--limit', metavar='N', help='除数の上限を指定します (既定:表の最大値)') args = parser.parse_args() table = args.N if len(table) == 1 and table[0] == -1: table = [] for n in range(random.randint(10, 50)): r = random.randint(0, 65535) if r not in table: table.append(r) limit = None if args.limit is None else int(args.limit) divisor = find_divisor(table, limit) if divisor <= 0: print('除数を見つけられませんでした') sys.exit(2) defval = len(table) if args.defval is None else int(args.defval) index = make_index(divisor, table, defval) nrem = tuple(n % divisor for n in table) srem = tuple('%d:%d' % (n, n % divisor) for n in table) print('除数:', divisor) print('逆引き:\n' + format_list(index, width=8, prefix=' '*4)[:-2]) print(('元の表: %d 個\n' % len(table)) + format_list(table, width=8, prefix=' '*4)[:-2]) print('剰余:\n' + format_list(nrem, width=8, prefix=' '*4)[:-2]) print('被除数:剰余:\n' + format_list(srem, width=8, prefix=' '*4)[:-2]) 実行結果(逆引きの8は無効を示す) $ python3 findivisor.py 1 3 5 7 11 13 17 19 除数: 11 逆引き: 4, 0, 5, 1, 8, 2, 6, 3, 7, 8, 8, 8 元の表: 8 個 1, 3, 5, 7, 11, 13, 17, 19 剰余: 1, 3, 5, 7, 0, 2, 6, 8 被除数:剰余: 1:1, 3:3, 5:5, 7:7, 11:0, 13:2, 17:6, 19:8 実行結果(逆引きの8は無効を示す) $ python3 findivisor.py 2 3 5 7 11 13 17 19 除数: 13 逆引き: 5, 8, 0, 1, 6, 2, 7, 3, 8, 8, 8, 4, 8, 8 元の表: 8 個 2, 3, 5, 7, 11, 13, 17, 19 剰余: 2, 3, 5, 7, 11, 0, 4, 6 被除数:剰余: 2:2, 3:3, 5:5, 7:7, 11:11, 13:0, 17:4, 19:6 テスト:数列として {-1} を与えると表は乱数生成になります。 実行結果(逆引きの-1は無効を示す) $ python3 findivisor.py -1 -d -1 除数: 63 逆引き: -1, -1, -1, -1, 12, -1, -1, -1, 4, -1, 8, 1, -1, -1, -1, -1, -1, 17, 3, 5, -1, -1, 18, -1, -1, -1, 14, -1, 6, -1, -1, 0, -1, -1, -1, 13, 2, -1, 10, -1, 16, -1, -1, 15, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 7, -1, -1, 9, -1, -1, -1, -1, -1 元の表: 19 個 33736, 35669, 21897, 61317, 8765, 55648, 42931, 16687, 37180, 19147, 8228, 49121, 40198, 23849, 36440, 27070, 10183, 42416, 57919 剰余: 31, 11, 36, 18, 8, 19, 28, 55, 10, 58, 38, 44, 4, 35, 26, 43, 40, 17, 22 被除数:剰余: 33736:31, 35669:11, 21897:36, 61317:18, 8765:8, 55648:19, 42931:28, 16687:55, 37180:10, 19147:58, 8228:38, 49121:44, 40198:4, 23849:35, 36440:26, 27070:43, 10183:40, 42416:17, 57919:22 HTML+JavaScript による生成 以下、スクリーン ショット (データは "19,2,17,3,13,5,11,7" を使用) 画像はバグってたバージョンのものです(正しくは穴埋め値は8) HTML <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" > <title>除数検索と逆引き表生成</title> <style> table { border: solid 1px gray; border-collaspe: collaspe; border-spacing: 0; } th, td { border: solid 1px gray; padding: 6px; } </style> </head> <body> <h1>除数検索と逆引き表生成</h1> <p> <textarea id="inp_table" rows="8" cols="80" placeholder="数列を入力"></textarea><br /> 配列の穴埋め値(既定は自動):<input id="inp_defval" type="text" size="8" value=""> <input type="button" value="実行" onclick="onUpdate();"> </p> <p>処理結果 => <span id="status">なし</span></p> <table id="result"></table> <p></p> <table id="out_table1"></table> <p></p> <table id="out_table2"></table> <p></p> <details> <summary>デバッグ用</summary> <div> <p>入力文字の分解:<span id="dbg_nums"></span></p> <p>上記の数値化:<span id="dbg_table"></span></p> <p>穴埋め値:<span id="dbg_defval"></span></p> <p>除数:<span id="dbg_divisor"></span></p> <p>逆引き:<span id="dbg_index"></span></p> </div> </details> <script type="text/javascript"> <!-- function setStatus(msg) { document.getElementById('status').innerText = msg; } function clearChildren(tag) { while (tag.firstChild) tag.removeChild(tag.firstChild); } function trAppendChild(tr, tag, msg) { const tx = document.createElement(tag); tx.innerText = msg; tr.appendChild(tx); return tx; } function trAppendTH(tr, msg) { return trAppendChild(tr, 'th', msg); } function trAppendTD(tr, msg) { return trAppendChild(tr, 'td', msg); } function trAppendTDRight(tr, msg) { const td = trAppendChild(tr, 'td', msg); td.style.setProperty('text-align', 'right'); return td; } function onUpdate() { setStatus('処理中'); const inptable = document.getElementById('inp_table'); const inpdefval = document.getElementById('inp_defval'); const nums = [...inptable.value.matchAll(/[ox_0-9A-Fa-f]+/g)]; const table = nums.map(s => Number(s)); if (table.length == 0) { setStatus('エラー:数列を入力してください'); return; } document.getElementById('dbg_nums').innerText = nums; document.getElementById('dbg_table').innerText = table; const tabchk = new Set(table); if (tabchk.size != table.length) { setStatus('エラー:重複があります'); return; } // 除数の検索と逆引き表の生成 const divisor_max = Math.max.apply(null, table); let divisor = table.length | 1; // if (divisor >= divisor_max) divisor = divisor_max | 1; let remains; for (; divisor < divisor_max; divisor += 2) { remains = table.map(n => n % divisor); if (new Set(remains).size == table.length) break; } const sindex = [...Array(divisor)].map(() => undefined); for (let i = 0; i < sindex.length; i++) sindex[remains[i]] = i; const ndefval = Number(inpdefval.value != '' ? inpdefval.value : '--'); const defval = (Number.isInteger(ndefval) ? ndefval : table.length); const index = sindex.map(n => (n != undefined) ? n : defval); // 結果出力 document.getElementById('dbg_defval').innerText = defval; document.getElementById('dbg_divisor').innerText = divisor; document.getElementById('dbg_index').innerText = index; { const otag = document.getElementById('result'); clearChildren(otag); { const tr = document.createElement('tr'); trAppendTH(tr, '除数と穴埋め値'); trAppendTD(tr, divisor + ',' + defval); otag.appendChild(tr); } { const tr = document.createElement('tr'); trAppendTH(tr, '逆引き表'); trAppendTD(tr, index); otag.appendChild(tr); } { const tr = document.createElement('tr'); trAppendTH(tr, '順引き表'); trAppendTD(tr, table); otag.appendChild(tr); } } { const otag = document.getElementById('out_table1'); clearChildren(otag); { const tr = document.createElement('tr'); trAppendTH(tr, '配列'); for (const d in table) trAppendTDRight(tr, d); otag.appendChild(tr); } { const tr = document.createElement('tr'); trAppendTH(tr, '入力'); for (const d of table) trAppendTDRight(tr, d); otag.appendChild(tr); } { const tr = document.createElement('tr'); trAppendTH(tr, '剰余'); for (const d of table) trAppendTDRight(tr, d % divisor); otag.appendChild(tr); } } { const otag = document.getElementById('out_table2'); clearChildren(otag); { const tr = document.createElement('tr'); trAppendTH(tr, '配列(剰余)'); for (const d in index) trAppendTDRight(tr, d); otag.appendChild(tr); } { const tr = document.createElement('tr'); trAppendTH(tr, '逆引き'); for (const d of index) trAppendTDRight(tr, d); otag.appendChild(tr); } { const tr = document.createElement('tr'); trAppendTH(tr, '入力'); for (const i in index) { let j = index[i]; const d = (0 <= j && j < table.length) ? table[j] : '-'; trAppendTDRight(tr, d); } otag.appendChild(tr); } } setStatus('完了'); } // !--> </script> </body> </html>
- 投稿日:2022-03-03T05:29:05+09:00
JavaScript基礎概念の勉強ノート
初めに このノートはJavaScriptを勉強して3ヶ月以来の振り返りであり、まだまだ勉強中の者ですので不適切な表現があればご指摘いただけると幸いです。 また、日本語と英語が混在して読みづらいと思いますが、どちらでも身につけたい能力として習慣化されるまで頑張って書いてみたいと考えています。どうかご了承ください。 #どちらもネイティブではありません プログラムの執行プロセス(大雑把な説明だけど) プログラム ⇒ Compiler(コンパイラ) ⇒ 機械語(0と1) 「変数」:値を記憶する場所、またはその名前(ものを置くボックス的な感じ) 「=」:「=」が1つの場合は、assign(与える・配属する)です。right-hand side(「=」の右側の値)をleft-hand side(「=」の左側の値)に与える・配属させるという意味です。記憶する場所というボックス(変数)に、値を保存する感じです。 var a = 123 console.log(a) // 123 console.log() ⇒ print(出力)。// ⇒ 注釈の書き方、出力の結果は123。logは数学のlogではなく、記録という意味でconsoleと合わせて「記録を出力」。 var b = 'hellow' // ''(single-quotation)の中はString(文字列) console.log(b + 'world') // hellow world データ型の識別はとても大事です。最初から覚えておけば楽だし、あとでコーディングの時もバグが少なくなるんです!✨ Array(配列) ある生徒のスコアを保存したいとき、さき学んだ書き方ですぐできる! var score1 = 65 var score2 = 30 var score3 = 80 console.log(score1 + score2 + score3) しかし学生が100人いたら、国語、数学、英語のスコアなど、100*3=300行を書きます?それはないんですよね...なので、Array(配列)を使いましょう! Array(配列)の書き方 var score = [] // 今は何も入っていない空きの配列 score[0] = 65 score[1] = 30 socre[2] = 80 Arrayは一連のデータ(値)を保存する場所。一連のデータから値を取り出すために座標が必要なので、[]の中で数字で表す。0から始まる世界です! console.log(score[0] + score[1] + score[2]) // 175 あれ?この書き方だけで値を入れたの?と疑問に思うかもしれないが、まずarrayはボックスの中のもの(値)に座標の付箋(index, インデックス)を付けて、座標によってもの(値)を呼び出す。 ↑を書き換えれば、↓のようになる var score = [65, 30, 80] console.log(score[0] + score[1] + score[2]) // 175 console.log(score) // [65, 30, 80] 注意点1:65, 30 , 80のデータ型はNumber、見た目のようにわかりやすく数字ですね! しかし’65', ‘30’ , ‘80’は数字?それとも…? 答えはString(文字列)です!’’(single-quotation)で包まれた値は、中身の内容に関わらず皆文字列です。 注意点2:arrayをコンソールしてもarrayです! 補足:Object ⇒ 対象または物を指す。これからObject/オブジェクトで書く。 if 条件式 このスコアは合格?不合格? if条件式で判断してみましょう! var score = 80 if (score >= 60) { console.log('良い夏休みを!') } else { console.log('補講で待ってるぞ') } これが「if条件式」基本の書き方です。 人間の言葉で訳すと、 もしも (この中がtrue・真実ならば) { この中の行動を実行してください } さもなければ { この中の行動を実行してください } 上の例は 80 >= 60 がtrueなので、console.log(‘良い夏休みを!’)が実行されて出力する。 if条件式は表計算ソフトの使い方とかなり似ているが、JavaScriptの中、一つケースが満足されたら、ほかのケースは実行しないという特性がある。つまり、 if (true) { do this } or { do this } ↑この書き方はpseudocode(擬似コード)と言います。 ケースが三つ以上の場合は下のように書きます。 var score = 59 if (score >= 60) { console.log('良い夏休みを!') } else if (score = 59) { console.log('見逃してあげようか...') } else { console.log('補講で待ってるぞ') } // コンソール出力:見逃してあげようか... また、JavaScriptはsingle thread(シングルスレッド)、処理の開始から終了まで1本の線で書けるような、枝分かれしない処理です。 例のif条件式、複数のケース条件を満たしても、一番上のケースだけ実行する。 (59だと、else ifとelseのケース条件どちらも満たしているが、一番上だけconsole。) var score = 59 if (score >= 60) { console.log('良い夏休みを!') } else if (score < 60) { console.log('補講で待ってるぞ') } else { console.log('見逃してあげようか...') } // コンソール出力:補講で待ってるぞ... Loop ループ処理 条件が満たされるまで、私は転生の輪廻から逃げられない…! (何とか小説のタイトルを真似してみたけど失敗した気がする?) While loop var i = 1 while (i < 10) { console.log(i) i++ // i = i + 1 or i += 1 } 補足:「i++」は、 i = i + 1 、または i += 1 と表している。 ループ処理は、終了条件が満たされるまで繰り返して反復処理してくれることです。 while (終了条件がfalseになる) { i を出力 iを、自分の値に+1という値をassign(与える) } これを全部実行するのがワンセットです!つまりこうなるよね。 i:私、1から頑張る! while:じゃ終了(false)までよろしくな~ i = 1 while (1 < 10) // true ⇒ 続行 { console.log(1) i++ // i = 1 + 1 → i = 2 // iという記憶する場所に、数字2を与える } (次のループから、 iが2になった。) i = 2 while (2 < 10) // true ⇒ 続行 { console.log(2) i++ // i = 2 + 1 → i = 3 } ... ... i = 10 while (10 < 10) // false ⇒ 終わる! { ... } この繰り返し処理のプロセスを実行することは、iteration(反復)と言います。 そして、反復処理したあとiを出力してみたら、 var i = 1 while (i < 10) { console.log(i) i++ } console.log(i) // 10 iという変数は所詮、値を記憶する場所として、目に見えない反復処理の中すでに何度も値が上書きされて変化している。 最後はi = 10、whileの終了条件(false)に満たしたら、脱出みたいな感じでループの輪廻からやっと逃げられて、そしてiの値が変わったままでプリントアウトされる。 For loop(for ループ) for (var i = 1; i <= 10; i++) { console.log(i) } console.log('i:', i) // この書き方は 'i: ' + i と同じ forループもwhileと同じ、ただ書き方が少し違って、開始条件と終了条件、そしてiterationの変数と計算の仕方も全部()に書きます。 補足:iはただの変数名、別の名前で宣告しても構わない。 // 処理のプロセス var i = 1; i <= 10; // true ⇒ 続行 console.log(i); // 1 i++ // 2 一番上の行に書かれても、プロセスの段階は一番最後です var i = 2; i <= 10; // true ⇒ 続行 console.log(i); // 2 i++ // 3 ... ... var i = 11; i <= 10; // false ⇒ loopから脱出、最後のconsoleに入る console.log('i:', i) // i: 11 ArrayとLoop var score = [35, 40, 70, 50, 85] var sum = 0 for (var i = 0; i < score.length; i++) { // .lengthは長さ console.log(score[i]) // i位置の値を出力 sum = sum + score[i] // 毎回sumの値に、scoreの中でi位置の値を足す } console.log(sum) // 280 Arrayは0からインデックスを付けるが、長さは物理的?に計算されるので、score[]は5つの値が入ってることから、長さは5です。iは0からカウントして、0, 1, 2, 3, 4、これも5回反復処理して、計算結果を出力した。 Function 関数式 関数式は、ある機械ボックスの中に指示されることをやる、呼び出されるまではじっとしていると自分が解釈している。他の数式と協同して作業すると壊れやすいから、できるだけ単純作業をやらせる。 // ↓こいつがaddという関数式ですよってJavaScriptに宣言する function add(a, b) { // aとbは引数(argument)、入れる値の代名詞で1つのとき省略可 console.log('a:', a, 'b:', b) console.log(a + b) return a + b // a+bの結果を返す } // 関数式add()を呼び出し、引数を入れる console.log(add(10, 20)) // 30 //引数は入れる位置に対応しながら関数式のプロセスに入る console.log(add(20, 10, 5)) // 30 // 引数は二つだけが必要なわけ return returnを書かないと一番下のconsoleはundefinedを出力する。なぜかというと、関数式はただ指示通りやっていて、returnを書かなかったらやることを済まして終わるだけ。 値がもらえない状態でconsoleはundefinedしか出力しない。(undefinedは存在するけど代入されてない、見つからない、または定義されてないという。関数式の場合は前者に当たって、変数名が定義されてないのが後者。)なので結果の値を返してほしいとき必ず書く。 補足:関数式のundefinedが存在するけど代入されてない、見つからないと言っているのは、この関数を確かに呼び出されて動作したけど、値が返されてないから見つからないんだよ!って感じているのでこの書き方をしました。? 関数式はループとほかの数式などと違い、プログラムの世界の空(上層)にいるので、地上(中層)に走ってるsingle thread(シングルスレッド)とは離れていて、どこに書かれても影響されません。シングルスレッドで呼び出される時だけ現れる。不思議な存在です。? (ちなみに深層部にはプロトタイプチェーン、コンパイラなどがいると、これらは自分が勝手に解釈している。) ノートを整理しながらこれを投稿して自分の振り返りにもためになるだなあと思って、とりあえずアウトプットしてみました!
- 投稿日:2022-03-03T00:15:58+09:00
すぐ使えるjsメモ
業務で必要となることが割とあるけど、コードを組むまでもない計算や変換のjsのメモとなります。 ブラウザのデベロッパーツールを起動してjsを実行するだけで結果がわかるので重宝してます。 現在時刻のUNIXタイム(ミリ秒)出力 Date.now(); // 1646227190720 new Date().getTime(); // 1646227352970 UNIXタイム(ミリ秒)から現在時刻へ new Date(1646227190720).toLocaleString('ja-JP'); // '2022/3/2 22:19:50' 文字列をURLエンコード encodeURI('カステラ'); // '%E3%82%AB%E3%82%B9%E3%83%86%E3%83%A9' URLエンコード文字列のデコード decodeURI('%E3%82%AB%E3%82%B9%E3%83%86%E3%83%A9'); // 'カステラ' JSON整形 var input = {"name":"カステラ","size":123,"item":[{"name":"梅","type":"small","price":100},{"name":"竹","type":"medium","price":500},{"name":"梅","type":"large","price":1000}]}; console.log(JSON.stringify(input, null, 4)); // 第三引数はインデント長 console.log(JSON.stringify(input, null, '\t')); // tabインデント Base64エンコード btoa(encodeURIComponent('taro:password01')); // 'dGFybyUzQXBhc3N3b3JkMDE=' Base64エンコード文字列のデコード decodeURIComponent(atob('dGFybyUzQXBhc3N3b3JkMDE=')); // 'taro:password01' 数値3桁カンマ区切り (12345556789).toLocaleString(); // '12,345,556,789' 先頭0埋め '123'.padStart(10, '0'); // 0000000123 ('0000000000' + 123).slice(-10); // 0000000123