20210427のJavaScriptに関する記事は26件です。

querySelectorAllとgetElementByClassNameの違い

はじめに querySelectorAllでは取得するノードをCSSのセレクタのように指定できる。便利です。こっちだけで充分...ではなかった。 とりあえずlengthプロパティが使えなかった。 見つけたら追記していく。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

~ChildプロパティをquerySelector/querySelectorAllで取得した値に使えるか

はじめに JavaScriptのDOMの勉強をし始めて、Elementと名の付くプロパティが使えたり使えなかったり大変だなあと感じています。 取得の仕方がとても重要そうなことを理解したので、今回はdocument.querySelectorとdocument.querySelectorAllで取得できるデータの形と、それぞれにNodeオブジェクトのプロパティ「.firstChild」「.lastChild」が使えるか試してみました。 結論 ①document.querySelectorで取得(Elementオブジェクト) →first/lastChildプロパティ利用可 ➁document.querySelectorAllで取得(NodeListオブジェクト) →first/lastChildプロパティ利用不可 ちなみに、 ③getElementByIdで取得(Elementオブジェクト) →利用可 ④getElementByTagName/ClassNameで取得(HTMLCollectionオブジェクト)利用不可 利用可の①と③はどちらもElementオブジェクトとして単一の要素を取得するようですね。 感想 傾向的に複数の要素を含むような取得の仕方をすると、使えなくなるプロパティが増えるようです。前回の記事↓ classやタグで取得したデータにgetElementsが使えない件
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScriptのEventフェーズについて Eventの発火の順番とイベントバブリング(伝播)

はじめに JavaScriptのeventについて重要な概念であるEvent Phasesについてまとめる。 その上でよく使われる以下について、それが何をするものなのかを解説する。 event.stopPropagation() JavaScriptのEvent Phases JavaScriptのイベントにはライフサイクルという概念があり3つに区分される。 Capturing Phase Target Phase Bubbling Phase 引用元:w3.org UI Events 上記の図と合わせてライフサイクルにおける各フェーズの意味については以下。 Capturing Phase:イベントが発生した場合にwindowからイベントターゲットまで下に向かって進んでいくフェーズ Target Phase:イベントターゲットに到達したフェーズ(状態) Bubbling Phase:その後、元進んできた所を順に戻っていくフェーズ JavaScriptのイベントはいつ発火するのか? 上記でみたように、JavaScriptのイベントには3つのフェーズがあるが、実際にイベントが発火するのはBubbling Phase※1。 event-phase.js document.addEventListener('click', function () { console.log('The document was clicked'); }); document.body.addEventListener('click', function () { console.log('The body element was clicked'); }); そのため上記のようなevent listenerを定義すると、console上には以下のように出力される。 ※documentとはhtml全体でありhtml > bodyの包含関係である。 The body element was clicked The document was clicked ※1 正確にはaddEventListener()の引数useCaptureがfalseである場合、Bubbling Phaseでイベントが発火する。 addEventListener()の引数useCaptureがtrueである場合、Capturing Phaseでイベントが発火する。 引数useCaptureは省略可能であり、デフォルトではfalseが設定されているものとして動く。 ※モダンなJavaScriptフレームワーク(React、Vue、Angularなど)ではaddEventListenerでイベント定義をする事はないのでここは気にする必要はなく、Bubbling Phaseでイベントが発火すると思って差し支えない。 Eventのバブリング(伝播) spanタグの文字列「反応しないでください」の上でマウスを動かしてイベントが発生する場面を考える。 この時、以下のように複数の要素に対して同じevent listenerを設定している場合、Bubbling Phaseで順番に戻る際にそれぞれのイベントが発火する。 つまり、親(p要素)・子(span要素)という関係で見ていくと、子がイベントターゲットなのでその子のイベントがまず発火。 その後Bubbling Phaseの動きで親要素に移りそこでも同じイベントが定義されているのでそのイベントが発火する、という事が起きる。 ※これを「イベントバブリング」と呼ぶ(日本語では「伝播」と言ったりする)。 event-bubbling.html <html> <body> <div id="parent">マウスをのせて下さい <span id="child">反応しないでください</span> </div> </body> </html> event-bubbling.js document.querySelector('parent').addEventListener('mousemove', function (event) { console.log('マウスをのせて下さい'); }); document.querySelector('child').addEventListener('mousemove', function (event) { console.log('反応しないでください'); }); event.stopPropagation()でイベントバブリング(伝播)を止める 上記のようなイベントバブリングが起きるケースで、id="parent"のイベントを発火させたくない場合には、event.stopPropagation()を用いる事でそれが実現できる。 ※stopPropagationは直訳すると「伝播を停止する」であり、つまりイベントバブリング(伝播)を止めるという事。 event-bubbling.js document.querySelector('parent').addEventListener('mousemove', function (event) { console.log('マウスをのせて下さい'); }); document.querySelector('child').addEventListener('mousemove', function (event) { event.stopPropagation() console.log('反応しないでください'); });
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScriptのEventフェーズについて Eventの発火順番とイベントメソッドの正体

はじめに JavaScriptのeventについて重要な概念であるEvent Phasesについてまとめる。 その上でよく使われる以下について、それが何をするものなのかを解説する。 event.stopPropagation() JavaScriptのEvent Phases JavaScriptのイベントにはライフサイクルという概念があり3つに区分される。 Capturing Phase Target Phase Bubbling Phase 引用元:w3.org UI Events 上記の図と合わせてライフサイクルにおける各フェーズの意味については以下。 Capturing Phase:イベントが発生した場合にwindowからイベントターゲットまで下に向かって進んでいくフェーズ Target Phase:イベントターゲットに到達したフェーズ(状態) Bubbling Phase:その後、元進んできた所を順に戻っていくフェーズ JavaScriptのイベントはいつ発火するのか? 上記でみたように、JavaScriptのイベントには3つのフェーズがあるが、実際にイベントが発火するのはBubbling Phase※1。 event-phase.js document.addEventListener('click', function () { console.log('The document was clicked'); }); document.body.addEventListener('click', function () { console.log('The body element was clicked'); }); そのため上記のようなevent listenerを定義すると、console上には以下のように出力される。 ※documentとはhtml全体でありhtml > bodyの包含関係である。 The body element was clicked The document was clicked ※1 正確にはaddEventListener()の引数useCaptureがfalseである場合、Bubbling Phaseでイベントが発火する。 addEventListener()の引数useCaptureがtrueである場合、Capturing Phaseでイベントが発火する。 引数useCaptureは省略可能であり、デフォルトではfalseが設定されているものとして動く。 ※モダンなJavaScriptフレームワーク(React、Vue、Angularなど)ではaddEventListenerでイベント定義をする事はないのでここは気にする必要はなく、Bubbling Phaseでイベントが発火すると思って差し支えない。 Eventのバブリング(伝播) spanタグの文字列「反応しないでください」の上でマウスを動かしてイベントが発生する場面を考える。 この時、以下のように複数の要素に対して同じevent listenerを設定している場合、Bubbling Phaseで順番に戻る際にそれぞれのイベントが発火する。 つまり、親(p要素)・子(span要素)という関係で見ていくと、子がイベントターゲットなのでその子のイベントがまず発火。 その後Bubbling Phaseの動きで親要素に移りそこでも同じイベントが定義されているのでそのイベントが発火する、という事が起きる。 ※これを「イベントバブリング」と呼ぶ(日本語では「伝播」と言ったりする)。 event-bubbling.html <html> <body> <div id="parent">マウスをのせて下さい <span id="child">反応しないでください</span> </div> </body> </html> event-bubbling.js document.querySelector('parent').addEventListener('mousemove', function (event) { console.log('マウスをのせて下さい'); }); document.querySelector('child').addEventListener('mousemove', function (event) { console.log('反応しないでください'); }); event.stopPropagation() とは イベントバブリング(伝播)を止める事ができる構文。 具体的には、上記のようなイベントバブリングが起きるケースで、id="parent"のイベントを発火させたくない場合には、event.stopPropagation()を用いる事でそれが実現できる。 ※stopPropagationは直訳すると「伝播を停止する」であり、つまりイベントバブリング(伝播)を止めるという事。 event-bubbling.js document.querySelector('parent').addEventListener('mousemove', function (event) { console.log('マウスをのせて下さい'); }); document.querySelector('child').addEventListener('mousemove', function (event) { event.stopPropagation() console.log('反応しないでください'); });
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScriptのEventフェーズについて Eventの発火順番とEventメソッドの正体

はじめに JavaScriptのeventについて重要な概念であるEvent Phasesについてまとめる。 その上でよく使われる以下について、それが何をするものなのかをまとめる。 event.stopPropagation() event.preventDefault() JavaScriptのEvent Phases JavaScriptのイベントにはライフサイクルという概念があり3つに区分される。 Capturing Phase Target Phase Bubbling Phase 引用元:w3.org UI Events 上記の図と合わせてライフサイクルにおける各フェーズの意味については以下。 Capturing Phase:イベントが発生した場合にwindowからイベントターゲットまで下に向かって進んでいくフェーズ Target Phase:イベントターゲットに到達したフェーズ(状態) Bubbling Phase:その後、元進んできた所を順に戻っていくフェーズ JavaScriptのイベントはいつ発火するのか? 上記でみたように、JavaScriptのイベントには3つのフェーズがあるが、実際にイベントが発火するのはBubbling Phase※1。 event-phase.js document.addEventListener('click', function () { console.log('The document was clicked'); }); document.body.addEventListener('click', function () { console.log('The body element was clicked'); }); そのため上記のようなevent listenerを定義すると、console上には以下のように出力される。 ※documentとはhtml全体でありhtml > bodyの包含関係である。 The body element was clicked The document was clicked ※1 正確にはaddEventListener()の引数useCaptureがfalseである場合、Bubbling Phaseでイベントが発火する。 addEventListener()の引数useCaptureがtrueである場合、Capturing Phaseでイベントが発火する。 引数useCaptureは省略可能であり、デフォルトではfalseが設定されているものとして動く。 ※モダンなJavaScriptフレームワーク(React、Vue、Angularなど)ではaddEventListenerでイベント定義をする事はないのでここは気にする必要はなく、Bubbling Phaseでイベントが発火すると思って差し支えない。 Eventのバブリング(伝播) spanタグの文字列「反応しないでください」の上でマウスを動かしてイベントが発生する場面を考える。 この時、以下のように複数の要素に対して同じevent listenerを設定している場合、Bubbling Phaseで順番に戻る際にそれぞれのイベントが発火する。 つまり、親(p要素)・子(span要素)という関係で見ていくと、子がイベントターゲットなのでその子のイベントがまず発火。 その後Bubbling Phaseの動きで親要素に移りそこでも同じイベントが定義されているのでそのイベントが発火する、という事が起きる。 ※これを「イベントバブリング」と呼ぶ(日本語では「伝播」と言ったりする)。 event-bubbling.html <html> <body> <div id="parent">マウスをのせて下さい <span id="child">反応しないでください</span> </div> </body> </html> event-bubbling.js document.querySelector('parent').addEventListener('mousemove', function (event) { console.log('マウスをのせて下さい'); }); document.querySelector('child').addEventListener('mousemove', function (event) { console.log('反応しないでください'); }); event.stopPropagation() とは イベントバブリング(伝播)を止める事ができる構文。 具体的には、上記のようなイベントバブリングが起きるケースで、id="parent"のイベントを発火させたくない場合には、event.stopPropagation()を用いる事でそれが実現できる。 ※stopPropagationは直訳すると「伝播を停止する」であり、つまりイベントバブリング(伝播)を止めるという事。 event-bubbling.js document.querySelector('parent').addEventListener('mousemove', function (event) { console.log('マウスをのせて下さい'); }); document.querySelector('child').addEventListener('mousemove', function (event) { event.stopPropagation() console.log('反応しないでください'); }); その他のEventメソッド event.preventDefault() とは HTMLの各要素の既定の動作を止める事ができる構文。 具体的には、<a>要素をクリックした際にリンクへ飛ぶのを止めるという事がevent.preventDefault()を用いる事で実現できる。 ※preventDefaultは直訳すると「デフォルトを妨げる」であり、つまり既定(デフォルト)の動作を止めるという事。 ※既定の動作の例 aタグの要素:クリックするとhrefで指定したリンク先へ飛ぶ inputタグでtype=checkboxの要素:クリックするとチェックが入る event-prevent.html <html> <body> <a id="link" href="https://google.com">Google</a> </body> </html> event-prevent.js document.querySelector('link').addEventListener('click', function (event) { event.preventDefault(); }); 参考文献 https://www.w3.org/TR/DOM-Level-3-Events/
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScriptのEventフェーズについて Eventの発火順番とEventメソッド

はじめに JavaScriptのEventについて重要な概念であるEvent Phasesについてまとめる。 その上でよく使われる以下について、それが何をするものなのかをまとめる。 event.stopPropagation() event.preventDefault() JavaScriptのEvent Phases JavaScriptのイベントにはライフサイクルという概念があり3つに区分される。 Capturing Phase Target Phase Bubbling Phase 引用元:w3.org UI Events 上記の図と合わせてライフサイクルにおける各フェーズの意味については以下。 Capturing Phase:イベントが発生した場合にwindowからイベントターゲットまで下に向かって進んでいくフェーズ Target Phase:イベントターゲットに到達したフェーズ(状態) Bubbling Phase:その後、元進んできた所を順に戻っていくフェーズ JavaScriptのイベントはいつ発火するのか? 上記でみたように、JavaScriptのイベントには3つのフェーズがあるが、実際にイベントが発火するのは、基本的にBubbling Phase※1。 event-phase.js document.addEventListener('click', function () { console.log('The document was clicked'); }); document.body.addEventListener('click', function () { console.log('The body element was clicked'); }); そのため上記のようなevent listenerを定義すると、console上には以下のように出力される。 ※documentとはhtml全体でありhtml > bodyの包含関係である。 The body element was clicked The document was clicked ※1 正確にはaddEventListener()の引数useCaptureがfalseである場合、Bubbling Phaseでイベントが発火する。 addEventListener()の引数useCaptureがtrueである場合、Capturing Phaseでイベントが発火する。 引数useCaptureは省略可能であり、デフォルトではfalseが設定されているものとして動く。 ※モダンなJavaScriptフレームワーク(React、Vue、Angularなど)ではaddEventListenerでイベント定義をする事はなく、falseの状態でイベント定義する事になるので、Bubbling Phaseでイベントが発火すると考えても問題ない(と思っている)。 Eventのバブリング(伝播) 以下のHTMLがある時、spanタグの文字列「反応しないでください」の上でマウスを動かしてイベントが発生する場面を考える。 この時、以下のように複数の要素に対して同じevent listenerを設定している場合、Bubbling Phaseで順番に戻る際にそれぞれのイベントが発火する。 つまり、親(div要素)・子(span要素)という関係で見ていくと、子がイベントターゲットなのでその子のイベントがまず発火する。 その後Bubbling Phaseの動きで親要素に移りそこでも同じイベントが定義されているのでそのイベントが発火する、という事が起きる。 ※これを「イベントバブリング」と呼ぶ(日本語では「伝播」と言ったりする)。 event-bubbling.html <html> <body> <div id="parent">マウスをのせて下さい <span id="child">反応しないでください</span> </div> </body> </html> event-bubbling.js document.querySelector('parent').addEventListener('mousemove', function (event) { console.log('マウスをのせて下さい'); }); document.querySelector('child').addEventListener('mousemove', function (event) { console.log('反応しないでください'); }); event.stopPropagation() とは イベントバブリング(伝播)を止める事ができる構文。 具体的には、上記のようなイベントバブリングが起きるケースで、id="parent"のイベントを発火させたくない場合には、event.stopPropagation()を用いる事でそれが実現できる。 ※stopPropagationは直訳すると「伝播を停止する」であり、つまりイベントバブリング(伝播)を止めるという事。 event-bubbling.js document.querySelector('parent').addEventListener('mousemove', function (event) { console.log('マウスをのせて下さい'); }); document.querySelector('child').addEventListener('mousemove', function (event) { event.stopPropagation() console.log('反応しないでください'); }); その他のEventメソッド event.preventDefault() とは HTMLの各要素の既定の動作を止める事ができる構文。 具体的には、<a>要素をクリックした際にリンクへ飛ぶのを止めるという事がevent.preventDefault()を用いる事で実現できる。 ※preventDefaultは直訳すると「デフォルトを妨げる」であり、つまり既定(デフォルト)の動作を止めるという事。 ※既定の動作の例 aタグの要素:クリックするとhrefで指定したリンク先へ飛ぶ inputタグでtype=checkboxの要素:クリックするとチェックが入る event-prevent.html <html> <body> <a id="link" href="https://google.com">Google</a> </body> </html> event-prevent.js document.querySelector('link').addEventListener('click', function (event) { event.preventDefault(); }); 参考文献 https://www.w3.org/TR/DOM-Level-3-Events/
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Event Phases(イベントフェーズ)について Eventの発火順番とEventメソッド

はじめに Webの話では必須なEventに関する重要な概念Event Phasesについてまとめる。 その上でよく使われる以下について、それが何をするものなのかをまとめる。 event.stopPropagation() event.preventDefault() Event Phases イベントにはライフサイクルという概念があり3つに区分される。 Capturing Phase Target Phase Bubbling Phase 引用元:w3.org UI Events 上記の図と合わせてライフサイクルにおける各フェーズの意味については以下。 Capturing Phase:イベントが発生した場合にwindowからイベントターゲットまで下に向かって進んでいくフェーズ Target Phase:イベントターゲットに到達したフェーズ(状態) Bubbling Phase:その後、元進んできた所を順に戻っていくフェーズ JavaScriptのイベントはいつ発火するのか? 上記でみたように、JavaScriptのイベントには3つのフェーズがあるが、実際にイベントが発火するのは、基本的にBubbling Phase※1。 event-phase.js document.addEventListener('click', function () { console.log('The document was clicked'); }); document.body.addEventListener('click', function () { console.log('The body element was clicked'); }); そのため上記のようなevent listenerを定義すると、console上には以下のように出力される。 ※documentとはhtml全体でありhtml > bodyの包含関係である。 The body element was clicked The document was clicked ※1 正確にはaddEventListener()の引数useCaptureがfalseである場合、Bubbling Phaseでイベントが発火する。 addEventListener()の引数useCaptureがtrueである場合、Capturing Phaseでイベントが発火する。 引数useCaptureは省略可能であり、デフォルトではfalseが設定されているものとして動く。 ※モダンなJavaScriptフレームワーク(React、Vue、Angularなど)ではaddEventListenerでイベント定義をする事はなく、falseの状態でイベント定義する事になるので、Bubbling Phaseでイベントが発火すると考えても問題ない(と思っている)。 Eventのバブリング(伝播) 以下のHTMLがある時、spanタグの文字列「反応しないでください」の上でマウスを動かしてイベントが発生する場面を考える。 この時、以下のように複数の要素に対して同じevent listenerを設定している場合、Bubbling Phaseで順番に戻る際にそれぞれのイベントが発火する。 つまり、親(div要素)・子(span要素)という関係で見ていくと、子がイベントターゲットなのでその子のイベントがまず発火する。 その後Bubbling Phaseの動きで親要素に移りそこでも同じイベントが定義されているのでそのイベントが発火する、という事が起きる。 ※これを「イベントバブリング」と呼ぶ(日本語では「伝播」と言ったりする)。 event-bubbling.html <html> <body> <div id="parent">マウスをのせて下さい <span id="child">反応しないでください</span> </div> </body> </html> event-bubbling.js document.querySelector('parent').addEventListener('mousemove', function (event) { console.log('マウスをのせて下さい'); }); document.querySelector('child').addEventListener('mousemove', function (event) { console.log('反応しないでください'); }); event.stopPropagation() とは イベントバブリング(伝播)を止める事ができる構文。 具体的には、上記のようなイベントバブリングが起きるケースで、id="parent"のイベントを発火させたくない場合には、event.stopPropagation()を用いる事でそれが実現できる。 ※stopPropagationは直訳すると「伝播を停止する」であり、つまりイベントバブリング(伝播)を止めるという事。 event-bubbling.js document.querySelector('parent').addEventListener('mousemove', function (event) { console.log('マウスをのせて下さい'); }); document.querySelector('child').addEventListener('mousemove', function (event) { console.log('反応しないでください'); event.stopPropagation(); }); その他のEventメソッド event.preventDefault() とは HTMLの各要素の既定の動作を止める事ができる構文。 具体的には、<a>要素をクリックした際にリンクへ飛ぶのを止めるという事がevent.preventDefault()を用いる事で実現できる。 ※preventDefaultは直訳すると「デフォルトを妨げる」であり、つまり既定(デフォルト)の動作を止めるという事。 ※既定の動作の例 aタグの要素:クリックするとhrefで指定したリンク先へ飛ぶ inputタグでtype=checkboxの要素:クリックするとチェックが入る event-prevent.html <html> <body> <a id="link" href="https://google.com">Google</a> </body> </html> event-prevent.js document.querySelector('link').addEventListener('click', function (event) { event.preventDefault(); }); 参考文献 https://www.w3.org/TR/DOM-Level-3-Events/
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【javascript】日付を指定の形式で出力

前置き JavaScript 環境ならどこでも使えそうな短いコードの日付書式出力関数。コピペ用。 - Qiita https://qiita.com/standard-software/items/fe8dfcbeb3bde37bd2f4 上記記事をきっかけに記事にしました。 詳細は上記記事をご覧ください。 コード 元記事のコメントに書いたものよりわかりにくくなってしまいました。 // d : [0.year, 1.month, 2.date, 3.hour, 4.minute, 5.sec, 6.msec, 7, 8.day] const dateFormats = { 'YYYY': d => d[0], 'YY': d => d[0].slice(-2), 'MM': d => d[1], 'M': d => +d[1], 'DD': d => d[2], 'D': d => +d[2], 'HH': d => d[3], 'H': d => +d[3], 'mm': d => d[4], 'm': d => +d[4], 'ss': d => d[5], 's': d => +d[5], 'fff': d => d[6], 'DDD': d => ['Sun', 'Mon', 'Tue', 'Wed', 'Thr', 'Fri', 'Sat'][d[8]], 'YMD': d => d.slice(0,3).join(''), 'Hms': d => d.slice(3,6).join(''), }; const dateToString = (format, ...args) => { const date = new Date(...args); const d = new Date(date - date.getTimezoneOffset()*60000) //修正 .toISOString().split(/[^\d]/).concat( date.getDay() ); return format.replace(/[YMDHhmsf]+/ig, m => dateFormats[m] ? dateFormats[m](d) : m); }; const dfmt = 'YYYY/MM/DD HH:mm:ss.fff(DDD)'; console.log( dateToString(dfmt) ); // 実行時の時間 console.log( dateToString(dfmt, 0) ); // 1970/01/01 09:00:00.000(Thr) console.log( dateToString(dfmt, '1999/12/31 23:59:59') ); // 1999/12/31 23:59:59.000(Fri) console.log( dateToString(dfmt, 1999, 11, 31, 23, 59, 59, 999) ); // 1999/12/31 23:59:59.999(Fri) 第1引数は出力形式の指定 第2引数以降はnew Date()の引数に入れる値と同じ。 私感 YYYYMMDDのような区切りなく連なった所は個別で処理できないのが問題点かもしれない。 コードに記したYMDのような形式を作ってしまえば対処は可能。 date.toISOString()さん、ローカル時間でも出力できるようになりませんかね。。。 そうすれば周辺スッキリするのに。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【javascript】日付を指定の形式で出力(追記あり)

前置き JavaScript 環境ならどこでも使えそうな短いコードの日付書式出力関数。コピペ用。 - Qiita https://qiita.com/standard-software/items/fe8dfcbeb3bde37bd2f4 上記記事をきっかけに記事にしました。 詳細は上記記事をご覧ください。 コード // d : [0.year, 1.month, 2.date, 3.hour, 4.minute, 5.sec, 6.msec, 7, 8.day] const dateFormats = { 'YYYY': d => d[0], 'YY': d => d[0].slice(-2), 'MM': d => d[1], 'M': d => +d[1], 'DD': d => d[2], 'D': d => +d[2], 'HH': d => d[3], 'H': d => +d[3], 'mm': d => d[4], 'm': d => +d[4], 'ss': d => d[5], 's': d => +d[5], 'fff': d => d[6], 'DDD': d => ['Sun', 'Mon', 'Tue', 'Wed', 'Thr', 'Fri', 'Sat'][d[8]], 'YMD': d => d.slice(0,3).join(''), 'Hms': d => d.slice(3,6).join(''), }; const dateToString = (format, ...args) => { const date = new Date(...args); const d = new Date(date - date.getTimezoneOffset()*60000) //修正 .toISOString().split(/[^\d]/).concat( date.getDay() ); return format.replace(/[YMDHhmsf]+/g, m => dateFormats[m] ? dateFormats[m](d) : m); }; const dfmt = 'YYYY/MM/DD HH:mm:ss.fff(DDD)'; console.log( dateToString(dfmt) ); // 実行時の時間 console.log( dateToString(dfmt, 0) ); // 1970/01/01 09:00:00.000(Thr) console.log( dateToString(dfmt, '1999/12/31 23:59:59') ); // 1999/12/31 23:59:59.000(Fri) console.log( dateToString(dfmt, 1999, 11, 31, 23, 59, 59, 999) ); // 1999/12/31 23:59:59.999(Fri) 第1引数は出力形式の指定 第2引数以降はnew Date()の引数に入れる値と同じ。 私感 YYYYMMDDのような区切りなく連なった所は個別で処理できないのが問題点かもしれない。 コードに記したYMDのような形式を作ってしまえば対処は可能。 date.toISOString()さん、ローカル時間でも出力できるようになりませんかね。。。 そうすれば周辺スッキリするのに。 区切りのない形式への対応(追記) YYYYMMDDのような形式です。 検索したところ、正規表現に後方参照を用いることで対応できそう。 //この部分を /[YMDHhmsf]+/g, //こう変更 /([YMDHhmsf])\1*/g, 結果は以下の通り const dfmt = 'YYYYMMDD HHmmss.fff(DDD)'; console.log( dateToString(dfmt, 0) ); // 19700101 090000.000(Thr) console.log( dateToString(dfmt, '1999/12/31 23:59:59') ); // 19991231 235959.000(Fri) console.log( dateToString(dfmt, 1999, 11, 31, 23, 59, 59, 999) ); // 19991231 235959.999(Fri) この場合では、YMDのような違う文字の混じった形式は使用できず、同じ文字の連続した形式のみに縛られるようになります。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JQueryで2回目イベントが発火しない

困ったこと ・クリックでイベント発火させたい ・初回は動いたが追加された要素のクリックイベントが発火されない 問題のコード $('botton').click(function(){ /* 処理内容 */ }; ボタンを押すことでイベント発火させ、ボタンを追加する処理をしました。 その次に上のようなスクリプトを用いて、 追加したボタンをクリック発火させよとしても発火しなくなりました。 原因 調べてみるとイベントハンドラが登録されていないからが原因でした。 HTMLを読み込み初回に表示する際にイベントハンドラが登録されるので、 それ以降に動的に追加された要素にはイベントハンドラが登録されず、 クリックしても動かないというものです。 解決方法 $(document).on('click', 'セレクタ', function(){ /* イベント発火時の処理 */ }); $(document).on のようにウェブページ全体に対してイベントハンドラを登録をすることで解決します。 記述を変更することで2回目もイベント発火しました! このような方法で動的に追加したセレクタでクリック以外のイベントも発火できます。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JSON5でJavaScriptオブジェクトのstringify

標準のJSON.stringifyは欲しいフォーマットで簡単に出力できなかったので、JSON5を使って見ました。 JSON5とは github The JSON5 Data Interchange Format (JSON5) is a superset of JSON that aims to alleviate some of the limitations of JSON by expanding its syntax to include some productions from ECMAScript 5.1. 欲しいフォーマット const obj = {a: 123, b: 'hello'}; console.log(JSON.stringify(obj, null, ' ')); 複数行で記載する場合、後ろにコンマが付く キーが文字列なので、クオテーションで括らない { a: 123, b: 'hello', } JSON.stringifyを使う場合 { "a": 123, "b": "hello", } JSON5.stringifyを使う場合 { a: 123, b: 'hello', } JSON5で流石に人間に優しくフォーマットしてくれますね。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScript でクッキーを作成

simple_cookie.html <!DOCTYPE html> <html lang="ja"> <head> <meta http-equiv="CONTENT-TYPE" content="text/html; charset=utf-8" /> <title>Cookie</title> <script type="text/javascript"> document.cookie = "aa='バナナ';SameSite=strict"; document.cookie = "bb='りんご';SameSite=strict"; document.cookie = "cc='みかん';SameSite=strict"; document.cookie = "message='こんにちは';SameSite=strict"; console.log(document.cookie); </script> </head> <body> <h2>Cookie</h2> <hr /> Apr/27/2021<br /> </body> </html> 実行結果
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScript Dateオブジェクトに書式指定メソッドを追加

以前作成したdate()関数をDateオブジェクト用のメソッドとして書き直したものです。 PHPのDateTime/DateTimeImmutableクラスのformatメソッドに近い使い方がしたかったのでメソッド名もformat()で合わせました。 formatパラメータ文字列は前回と同様、基本的にPHPのものに合わせていますが、e タイムゾーン識別子、T タイムゾーンの略称、I サマータイム等、未対応のものもあります。 script date_format.js 'use strict'; Date.prototype.format = function(format) { if(isNaN(this)) return this; if(format === undefined) return ''; const time = Math.abs(this.getTime() - Date.now()) < 100 && typeof performance !== 'undefined' && performance.timeOrigin !== undefined ? performance.timeOrigin + performance.now() : this.getTime(); const date = new Date(time); date.setMinutes(date.getMinutes() - date.getTimezoneOffset()); const p = date.toISOString().match(/\d+/g); const d = { year : p[0], month : p[1], day : p[2], hour : p[3], minute: p[4], second: p[5], millisecond: p[6], }; const spellingMonth = [ 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'][d.month - 1], spellingWeek = [ 'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'][this.getDay()], adj = new Date(+this - this.getTimezoneOffset() * 6e4), sDate = new Date(Math.floor((+adj + 2592e5) / 6048e5) * 6048e5), sYear = sDate.getUTCFullYear(); return format .replace(/(\w)/g, ':$1:') .replace(/\\:(\w):/g, '#$1#') // RFC2822フォーマットされた日付 .replace(/:r:/g, ':D:, :d: :M: :Y: :H:::i:::s: :O:') // ISO8601 日付 .replace(/:c:/g, ':Y:-:m:-:d:#T#:H:::i:::s::P:') // 年 4桁 .replace(/:Y:/g, d.year) // 月 2桁 .replace(/:m:/g, d.month) // 日 2桁 .replace(/:d:/g, d.day) // 時 24時間単位 2桁 .replace(/:H:/g, d.hour) // 分 2桁 .replace(/:i:/g, d.minute) // 秒 2桁 .replace(/:s:/g, d.second) // 年 2桁 .replace(/:y:/g, d.year.slice(-2)) // 月 0埋め無し .replace(/:n:/g, +d.month) // 日 0埋め無し .replace(/:j:/g, +d.day) // 時 24時間単位 0埋め無し .replace(/:G:/g, +d.hour) // 時 12時間単位 2桁 .replace(/:h:/g, ('0' + ((+d.hour + 11) % 12 + 1)).slice(-2)) // 時 12時間単位 0埋め無し .replace(/:g:/g, (+d.hour + 11) % 12 + 1) // 閏年か 0:閏年ではない 1:閏年 .replace(/:L:/g, new Date(d.year, 2, 0).getDate() === 29 ? 1 : 0) // 日に対する英語形式序数サフィックス .replace(/:S:/g, ['th', 'st', 'nd', 'rd'] [Math.floor(d.day / 10) % 10 !== 1 && d.day % 10 < 4 ? d.day % 10 : 0]) // 曜日 0:日~6:土 .replace(/:w:/g, this.getDay()) // ISO8601 曜日 1:月~7:日 .replace(/:N:/g, (this.getDay() + 6) % 7 + 1) // 年間通算日 0~365 .replace(/:z:/g, Math.floor(date.getTime() / 864e5 - new Date(d.year + '-01-01T00:00:00Z').getTime() / 864e5)) // 月の日数 .replace(/:t:/g, new Date(d.year, d.month, 0).getDate()) // ISO8601 週番号による年 .replace(/:o:/g, sYear) // ISO8601 月曜日に始まる年単位の週番号 .replace(/:W:/g, ('0' + (1 + Math.floor(( sDate - new Date(sYear + '-01-01T00:00Z')) / 6048e5))).slice(-2)) // 曜日 3文字 .replace(/:D:/g, spellingWeek.slice(0, 3)) // 月 3文字 .replace(/:M:/g, spellingMonth.slice(0, 3)) // 曜日 フルスペル .replace(/:l:/g, spellingWeek) // 月 フルスペル .replace(/:F:/g, spellingMonth) // am/pm .replace(/:a:/g, d.hour < 12 ? 'am' : 'pm') // AM/PM .replace(/:A:/g, d.hour < 12 ? 'AM' : 'PM') // GMTとの時差 ±HH:MM .replace(/:P:/g, this.toString().match(/[+-]\d{4}/g)[0].replace(/(..)$/, ':$1')) // GMTとの時差 Pと同じ 但し+00:00はZ .replace(/:p:/g, this.toString().match(/[+-]\d{4}/g)[0].replace(/(..)$/, ':$1') .replace('+00:00', 'Z')) // GMTとの時差 ±HHMM .replace(/:O:/g, this.toString().match(/[+-]\d{4}/g)[0]) // タイムゾーンオフセット秒数 .replace(/:Z:/g, -this.getTimezoneOffset() * 60) // UNIXエポックからの秒数 .replace(/:U:/g, Math.floor(time / 1000)) // ミリ秒 .replace(/:v:/g, d.millisecond) // マイクロ秒 .replace(/:u:/g, ('00000' + Math.floor((time % 1000) * 1000)).slice(-6)) // Swatchインターネットタイム .replace(/:B:/g, ('00' + (Math.floor(((time + 36e5) % 864e5) / 86400))).slice(-3)) .replace(/:(\w):/g, '$1') .replace(/#(\w)#/g, '$1'); } パラメータ文字置換のreplace()メソッドチェーンが多いですが、不要なものは削ってしまっても大丈夫です。 使用法 <script src='date_format.js'></script> <script> const date = new Date(); console.log(date.format('Y-m-d H:i:s')); // 2021-04-27 17:54:53 </script> sample <!DOCTYPE html> <html lang='ja'> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <meta name="viewport" content="width=device-width,initial-scale=1"> <script src='date_format.js'></script> </head> <body> <div id='v'></div> <script> d = document.getElementById('v'); function test() { d.innerText = new Date().format('Y-m-d H:i:s.u\nr\nc\nU\nB'); requestAnimationFrame(test); } test(); </script> </body> </html> 動作デモ sample2 const date = new Date('2021/06/07 01:23:45'); console.log(date.format('r / c')); console.log(date.format('Y-m-d H:i:s')); console.log(date.format('y-n-j G:i:s h g')); console.log(date.format('D M l F a A Z U P p')); console.log(date.format('曜日:w 曜日(1~7):N 年間通算日(0~365):z 月の日数:t')); console.log(date.format('閏年か:L 日に対するサフィックス:S')); console.log(date.format('\\i\\s\\o8901年/週 o-W')); console.log(date.format('\\S\\w\\a\\t\\c\\hインターネットタイム:B')); console.log(date.format('ミリ秒:v マイクロ秒:u')); console.log(new Date('2000-01-01T00:00:00.123456+09:00').format('ミリ秒:v マイクロ秒:u')); console.log(new Date().format('ミリ秒:v マイクロ秒:u')); // Mon, 07 Jun 2021 01:23:45 +0900 / 2021-06-07T01:23:45+09:00 // 2021-06-07 01:23:45 // 21-6-7 1:23:45 01 1 // Mon Jun Monday June am AM 32400 1622996625 +09:00 +09:00 // 曜日:1 曜日(1~7):1 年間通算日(0~365):157 月の日数:30 // 閏年か:0 日に対するサフィックス:th // iso8901年/週 2021-23 // Swatchインターネットタイム:724 // ミリ秒:000 マイクロ秒:000000 // ミリ秒:123 マイクロ秒:123000 // ミリ秒:275 マイクロ秒:275893 マイクロ秒の下3桁はDateオブジェクトが現在日時以外の場合は000になります。また、現在日時であっても環境によってはマイクロ秒まで取得できず000になる場合があります。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

React-Leafletでマップを表示

はじめに React で地図を表示させ、その上にマーカーを表示させる。React Leaflet を参考に進めた。React の基本的な知識は知っている前提とする。 React-Leaflet のインストール 以下コマンドで、インストールできる。 $ npm install react react-dom leaflet $ npm install react-leaflet leaflet コンポーネントの作成 指定座標を中心に地図を表示させ、さらにマーカーを配置するコンポーネントを作成する。任意のディレクトリで SimpleLeaflet.js というファイルを作成し、以下コードを書く。ここでは components/Leaflet ディレクトリの直下に置いた。useState を使うことで指定位置やズームを変更することを見据えて position や zoom を作成しているが、この記事ではとくに用いていない。 SimpleLeaflet.js import React, { useState } from "react"; import "leaflet/dist/leaflet.css"; import { MapContainer, TileLayer, Marker, Popup } from "react-leaflet"; const SimpleLeaflet = () => { const [position, setPosition] = useState({ lat: 51.505, lng: -0.09, }); const [zoom, setZoom] = useState(13); return ( <div> <MapContainer center={position} zoom={zoom} style={{ height: "50vh" }}> <TileLayer attribution='&amp;copy <a href="http://osm.org/copyright";>OpenStreetMap</a> contributors' url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" /> <Marker position={position}> <Popup> A pretty CSS3 popup. <br /> Easily customizable. </Popup> </Marker> </MapContainer> </div> ); }; export default SimpleLeaflet; 上記コンポーネントを作成後、App.js などに import SimpleLeaflet from "components/Leaflet/SimpleLeaflet.js"; でコンポーネントを読み込み、出力したい場所で <SimpleLeaflet /> とすれば以下のように地図を表示することができる。 import "leaflet/dist/leaflet.css"; での CSS を読み込みや、<MapContainer ... style={{ height: "50vh" }}> で高さの指定をしない場合は地図が適切に表示されないので注意。 上記ではマーカーがまだ表示されていない。調べたところ、Marker not appearing for simple example #453 を発見。これを参考にコードを追記したところ、マーカーを表示できた。 SimpleLeaflet.js import React, { useState } from "react"; import "leaflet/dist/leaflet.css"; import { MapContainer, TileLayer, Marker, Popup } from "react-leaflet"; // add icons import Leaflet from "leaflet"; import icon from "leaflet/dist/images/marker-icon.png"; import iconShadow from "leaflet/dist/images/marker-shadow.png"; // marker setting let DefaultIcon = Leaflet.icon({ iconUrl: icon, shadowUrl: iconShadow, }); Leaflet.Marker.prototype.options.icon = DefaultIcon; const SimpleLeaflet = () => { const [lat, setLat] = useState(51.505); const [lng, setLng] = useState(-0.09); const [zoom, setZoom] = useState(13); const [position, setPosition] = useState({ lat: 51.505, lng: -0.09, }); return ( <div> <MapContainer center={position} zoom={zoom} style={{ height: "50vh" }}> <TileLayer attribution='&amp;copy <a href="http://osm.org/copyright";>OpenStreetMap</a> contributors' url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" /> <Marker position={position}> <Popup> A pretty CSS3 popup. <br /> Easily customizable. </Popup> </Marker> </MapContainer> </div> ); }; export default SimpleLeaflet; クリックすると、ポップアップが表示されることも確認できる。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

classやタグで取得したデータにgetElementsが使えない件

はじめに 今回の問題は let element = document.getElementsByClassName('profile-content'); このようにクラス属性で取得したデータの中から、spanタグの箇所のみを入手しようとして let element = document.getElementsByClassName('profile-content');//さっきのコード let profilespan = element.getElementsByTagName('span'); と書いたところ、下のelement.getElementsByTagNameがnot a function(関数じゃない)と怒られたことです。 原因としてはおそらく、上のクラスで取得したデータ型がHTMLCollectionであり、それに対してgetElementsメソッドが利用できないためです。どうすればよいかというと let element = document.getElementById('id名');//elements(複数形)じゃないよ let profilespan = element.getElementsByTagName('span'); とするのが正しいようです。クラスではなくidで取得しようとのことでした。 なんだか不便な気もしますが、これで一応解決です。どなたかの助けになれば幸いです。 まとめ idで取得する getElementById => Elementオブジェクトで取得取得 タグで取得する getElementsByTagName => HTMLCollectionオブジェクトで取得 クラスで取得する getElementsByClassName => HTMLCollectionオブジェクトで取得 .elementsメソッドは上記のうちElementオブジェクトにのみ適用可能。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

(つらいけど) JavaScript 生で RFC3339 な文字列を生成する方法

RFC3339 という日時の記法があります。 1985-04-12T23:20:50.52Z こういった書き方で日時を割り出します。 これを、ライブラリなしの JavaScript で表現しようとすると、実は大変です。 // Unixtime で指定したい場合はミリ秒で渡すこと const date = new Date(1619505636 * 1000); const timezoneOffset = date.getTimezoneOffset(); const timezoneHours = timezoneOffset / 60; const timezoneMinutes = timezoneOffset % 60; const rfc3339 = [ date.getFullYear(), '-', (`0${(date.getMonth() + 1)}`).slice(-2), '-', (`0${date.getDate()}`).slice(-2), 'T', (`0${date.getHours()}`).slice(-2), ':', (`0${date.getMinutes()}`).slice(-2), ':', (`0${date.getSeconds()}`).slice(-2), (-timezoneHours < 0 ? '' : '+'), (`0${-timezoneHours}`).slice(-2), ':', (`0${timezoneMinutes}`).slice(-2), ].join(''); console.log(rfc3339); // outputs: 2021-04-27T06:40:36+00:00 // https://ideone.com/4OzmFl つらいなあ。 タイムゾーンは Date クラスでは「現在のランタイムのロケール」に依存します。 Intl.DateTimeFormat を用いればロケール対応が可能らしいです。 ちなみに、 moment.js は開発をストップしメンテナンスモードとなっているため、後発の Luxon や Day.js などのより軽量で安全なライブラリを使うことが推奨されています。 ちなみに PHP で同じことを実現しようとすると... <?php // Unixtime で指定したい場合は @ を最初につけること $now = new DateTime('@1619505636'); echo $now->format('Y-m-d\TH:i:sP'); // outputs: 2021-04-27T06:40:36+00:00 // https://ideone.com/hwVWqJ まあこれくらいは一行でできて当然よね...?
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Twitter風文字カウントを実装してみた

筆者は社内SE業務で、Twitter風の文字超過をハイライトする入力欄を作成した。 その成果をここに共有する。 どうせならQiitaで投稿できるように、徹底的にTwitterを真似てしまえということで、Twitterの文字計測をそのままコードに落とし込んだ(社内SE成果物とは数え方が違うので悪しからず)。 ASP.NET MVCで作成したアプリ経由でTwitterに投稿したい場合には使えるはず(その手のサポートをしてくれる会社のサービスを利用する方が楽だろうが)。ライセンスはMITライセンスなので、自由に使用可能だ。 実装方法 使用したのはASP.NET MVC 5(ASP.NET CoreではTagBuilderの仕様が異なり、ToString(TagRenderMode)が読みだせないようなので、GenerateElementの書き直しが必要)。.Netのバージョンは4.6.1。C#により入力欄と超過部分のテンプレート生成をしている。実際にテンプレートを運用するのはJavaScriptの仕事。ということで責任の重さはC#:JS = 3:7程度である。 実際のコードはGitHubをご覧いただくとして、こちらでは使い方やコードの重要な部分を解説する。 使い方 HTMLヘルパーとしてCharactersCountingDivHelperを登録する。ビューのweb.configのsystem.web.webPages.razor > pages > namespaces要素に<add namespace="(プロジェクト名).(ヘルパーを入れたフォルダ)"/>でヘルパーを登録できる。 BundleConfig.csのRegisterBundles関数にbundles.Add(new ScriptBundle("~/bundles/countInput").Include("~/Scripts/input/count_input.js"));を設定する。 ヘルパーを利用するビューにスクリプトを追加する。宗教的理由で遅延読み込みを使っているので、利用時は<script src="@(BundleTable.Bundles.ResolveBundleUrl("~/bundles/countInput"))" defer></script>と入力する必要がある。これからのJSはdeferが当たり前になるはずなので、今の内に慣れるのが重要だと思う。 フォーム内で@Html.TwitterLikeInputForを使用する。名前がForで終わるのはASP.NET MVC純正のHTMLヘルパーに合わせた形。 @Html.TwitterLikeInputForの引数は5つ。 maxLength(入力欄の最大文字数。半角基準で入力) wrapperHtmlAttributes(ラッパーのHTMLプロパティ。idは指定しても無視される) editorHtmlAttributes(編集箇所のHTMLプロパティ。id、controleditableは指定しても無視される) validationHtmlAttributes(編集箇所のHTMLプロパティ) excessiveStringAttributes(超過部分のHTMLプロパティ) 引数名にAttributesとあるものはいずれもDictionary<string, object>で指定。これはこの手のヘルパーでよくつかわれるObjectだとリフレクションを使わなければならないので、それを避けるという信条の問題がある。名前付き引数で設定すると幸せになれる。 実装の解説 ヘルパー(ASP.NET MVC) オプション引数を使うことでHTML属性の設定を任意に行える。筆者の環境のC#が古いため、属性の初期値設定が面倒なことになっているが、現在はwrapperHtmlAttributes ??= new Dictionary<string, object>();で一発入力できる。ただ、どうせならオプション引数に直接入れられるとNullReferenceException問題が無いのでうれしいのだが。おそらくCLIの変更も必要なので難しいと思われるが、MSには対応していただきたい。 最大文字数も(Twitter風なので意味は無いが)初期値を入力可能にしている。文字数カウントの仕様が独特なためMaxLength属性はあえて使用していない。つまり、文字数のバリデーションは完全にJSの担当である。 後はHtmlHelperの拡張メソッドとTagBuilderを使って各種要素の生成をしているだけである。 編集箇所の方はcontenteditableをtrueにすることで直接編集可能にしている。また、idを指定することで、JSから読み出せるようにしている。さらに、クラスにeditorを追加することで、編集箇所ということを分かりやすくしているので、既存HTMLとCSSにバッティングしないように注意! 属性を直接指定できる要素の他にも、文字数カウンターも生成している。 JavaScript Visual Studio 2015の非力かつ時代遅れ(例えばfor (const foo of bar)に対応していない!)のIntellisense機能を利用してのJSコーディングは疲れたぜ。 大発見。hogeもしくはpiyoのどちらかで分割させて、なおかつ両方とも文字列として残したい場合、正規表現はこう書く。 ((?:hoge)|(?:piyo)) この?:が無いと、例えばhogeがマッチングした際にpiyoのマッチングも検証され、見つからないためnull配列が生成されてしまう(逆も然り。説明が不正確なのはご容赦を)。今回最大のハマりポイント!ネットにも情報が転がっていなかったので、ちょっと自慢したい。 本来分割文字(CJK文字(ひらがな、カタカナ、漢字、ハングル)および空白文字)の正規表現はCJK文字の正規表現と結合したかったが、うまくいかずに繰り返して書いている。無念。 なお、後読み(?<=hoge)を使えればURLの検出が簡単になった1のだが、さすがにSafariの対応はしておいた方がいいなというのと、Twitter自身がしていることと違うだろうということで没に。 正規表現でその他注目なのは、[\p{Script=Katakana}]でUnicodeカタカナの文字グループを一気に読みだしているところ。SafariやChromeが対応し、V8の正規表現エンジンをFireFoxが導入したことで、ほとんどのWebブラウザがこの記法を使用可能となり、IE(と旧Edge)の互換性と引き換えに[\u]で直接文字コードを指定する手間がかなり削減されたうえ、正規表現が分かりやすくなった。ただし、句読点などは文字グループが見つからなかったため直接指定している。 inputイベントのisComposingがtrueかどうかを調べることで、日本語や中国語を入力時は変換確定後、アルファベットなどは入力ごとに文字数チェックが走るようにしている。 文字数カウントは「CJK文字」(1文字につき1)「URL」(長さにかかわらず11.5文字固定)「それ以外」(1文字につき0.5)でカウント方法を分けている。内部的には整数で扱いたいため、倍でカウントしている(おそらくTwitterもそういう風に実装している)。 最大文字数を超過した場合、文字列を分割するメソッドを走らせる。文字列を分割文字で配列に分けて、配列ごとの文字を数え、通常の文字列に追加していく。最大文字数を超過する部分に遭遇したらそこで分割位置を計算(最大文字数と直前の文字数の差が分割位置になる)、分割位置よりも前は通常の文字列、それ以降は超過文字列に分ける。以降の配列はすべて超過文字列に投入する。最後に、通常の文字列は編集箇所のテキストノードに、超過文字列は超過部分用の<span>に入れ、カウンターを赤字にする。 サブミット時に、編集箇所の内容をinputに代入するのもJSの責務だ。編集箇所をgetElementsByClassNameで抽出した後、超過部分のspanが存在しない編集箇所のtextContentをinputのvalueに入れる。超過部分が存在する場合はエラーを出すようにする。 反省箇所 文字カウントはCJK文字以外の文字数をベースにしている。その方が処理が分かりやすいと思ったが、よくよく考えれば直感的ではなかった。変更はリスクが高いので、今更引き返せない。 HTMLの要素生成にTagBuilderを利用しているが、HTML要素を直接入れ子にできない2ので扱いが難しい。AngleSharpを勉強して活用したほうが後々のためになったような気がする。 要素全体を囲むラッパーにidを指定しているが、よくよく考えたらラッパーで何かするというのも思いつかないし、余計な機能だった気がする。作った時に何を考えてたんだろう、私… 編集箇所のクラス名を、もう少し凝った、被りづらい名前にすべきだったと思う。 カウンターのHTML属性を指定できた方が良かった。 と挙げてみたところ、反省箇所はおおむね設計の問題に集中している。コード自体はパフォーマンスの問題(例えば文字カウントを超過判定・仕分けの2回行っている)など思うところはあるが、ほぼほぼクリーンに書けていると思う。問題点があったらIssuesやプルリクの投稿をお願いします。 まとめ 実装したい機能の概略は割とシンプルだったが、特にJSは考えさせられる場面が多々あり、文字カウントの方法や正規表現は難しかった。この文字カウントを実装したTwitterのエンジニアは本当に頭がいいと思った。 コードを実際に読まずに、与えられた仕様で実装するのは、エンジニアの地頭を鍛えるエクササイズとしては良いものだった。 惜しむらくは、後先考えずに設計をおろそかにしてしまったため、使いづらそうな部分が多いということか。問題点があったらIssuesやプルリクの投稿をお願いします(大事なことなので2回言いました)。 皆さんも、自分のTwitter風文字カウントを作ってほしいと思う。 参考 コーディング及び記事作成時に参考にしたサイトを挙げる。 Twitter文字数カウント5ルール | URLや改行は何文字? - 作業ロケット javascript - How can I use the script defer attribute for ASP MVC 4 Bundles with Scripts.Render - Stack Overflow 正規表現 | 先読みと後読みを使ったパターン - Let'sプログラミング 正規表現 \p{...} メモ - Qiita [javascript]URLを取得する正規表現 - Qiita Mozilla、今後はV8の正規表現エンジンをFirefoxにそのまま取り込むと表明。そのための互換レイヤを開発 - Publickey 履歴 後読みを利用した正規表現の式がおかしかったので修正。 文頭にURLが来るか、CJK文字もしくは空白の直後にURLが来るというのを、後読みを利用して書くとこうなる。/(?<=^|[\p{Script=Katakana}\p{Script=Hiragana}\p{Script=Han}\p{Script=Hangul}\u{3000}-\u{303F}\u{FE30}-\u{FE4F}]+|\s+)(https?:\/\/[-_.!~*'()a-zA-Z0-9;\/?:@&=+\$,%#]+)/gu ↩ 入れ子にしたい内容を一旦テキストに変換して、入れたいtagBuilderのInnerHtmlに代入するか、開始タグ・終了タグを別個に生成して入れ子にしたい内容をテキスト化する際に挟み込む。今回は後者を採用している。 ↩
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

DOM操作

概要 日付、税別価格、仕込み価格のデータをテーブルデータに反映させる。 HTML index.html <tr> <th>日付け</th><th>税抜</th><th>結果</th> </tr> <tr> <th id="day">データ</th><th id="taxNo">データ</th><th id="total">データ</th> </tr> 今回は、こちらのテーブルデータに書き換えを行っていきます。 JS index.js const hizuke = date(); const sub = totalTax(); const noTax = document.getElementById('number').value; const win =[hizuke,noTax,sub]; document.getElementById('day').textContent = win[0]; document.getElementById('taxNo').textContent = win[1]; document.getElementById('total').textContent = win[2]; データ内容を配列に格納しました。 それぞれの格納場所にデータを配置していきます。 配列からのアクセス 現在winの中にはデータとしてhizuke,notax,subが格納されています。 ここから各テーブルの中に代入していきます。 ①日付け欄にwinの0番目の値、hizukeを代入します。 ②税別欄にwinの1番目の値、notaxを代入します。 ③税込み欄にwinの値、subを代入します。 *配列は先頭から0番始まりです。 データがすべて反映されました。 今回やりたいこととしては、データ反映とそれに伴い行を増やすことです。 for文とdocument.createElementを使って行きたいと思います。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

nuxt.js × three.js × ammo.js でSpeedPizzaを作った時のメモ(失敗)

概要 nuxt.js × three.js × ammo.js の開発に関するメモ。 外部OBJファイル等をloadしてsceneにaddしてから、それに対して、 物理演算(自由落下と当たり判定)を物理エンジンを使って加えられるかの実験メモ。 結論から言うと、今回は失敗します。 はじめに 3Dのpizzaサイト(?)を作ることになった。 とりあえずthree.jsを使って、ついでにammo.jsも使って、3D物理エンジンをいじってみよう。 そんでベースは、最近もっぱらnuxt.js。(筆者はts苦手なのでjs使いますよ。) ということで、nuxt.js × three.js × ammo.jsで開発することにする。 前提 全体的に基本的な情報は書いてないです。 筆者はnuxt.jsとthree.jsに関しては色々触ってきましたが、 ammo.jsというか、物理エンジンは全く触ったことがないです。 バージョン node.js : v12.19.0 npm : v6.14.8 環境構築 nuxt.jsの環境構築は、スキップ。 three.jsは、npmでインストール。 ammo.js は、wasm(WebAssemblyファイル)を持ってきてstatic/js/に置いて、config内に外部読み込みを記載。 それで、できたpackage.jsonとnuxt.config.jsは参考程度に以下に記載。 package.json package.json { "name": "speed_pizza", "version": "1.0.0", "private": true, "scripts": { "analyze": "nuxt build --analyze", "dev": "cross-env NODE_ENV=development nodemon server/index.js --watch server", "pro": "cross-env NODE_ENV=production node server/index.js", "build:dev": "cross-env NODE_ENV=development nuxt build", "build:prod": "cross-env NODE_ENV=production nuxt build", "generate": "nuxt generate", }, "dependencies": { "core-js": "^3.6.5", "cross-env": "^7.0.2", "dotenv": "^8.2.0", "express": "^4.17.1", "nuxt": "^2.14.6", "three": "^0.123.0", "vue": "^2.6.12", }, "devDependencies": { "@nuxtjs/style-resources": "^1.0.0", "@nuxtjs/stylelint-module": "^4.0.0", "node-sass": "^5.0.0", "nodemon": "^2.0.6", "nuxt-svg-loader": "^1.2.0", "sass-loader": "^10.1.0", } } nuxt.config.js nuxt.config.js module.exports = { srcDir: './src/', globalName: 'speedpizzaapp', env: { NODE_ENV: process.env.NODE_ENV, }, html: { prefix: 'og: http://ogp.me/ns#', lang: 'ja', }, head: { title: process.env.NODE_ENV === 'production' ? 'SpeedPizza(3D)' : '【開発】SpeedPizza(3D)', meta: [ { charset: 'utf-8' }, { hid: 'description', name: 'description', content: 'SpeedPizza is the first service in Japan that enables you to produce the world\'s number one popular dish, pizza, as a virtual 3D object from your smartphone at any time.' }, { hid: 'keywords', name: 'keywords', content: '' }, { 'http-equiv': 'X-UA-Compatible', content: 'IE=edge' }, { name: 'viewport', content: 'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no', }, { name: 'format-detection', content: 'telephone=no' }, { property: 'og:description', content: 'SpeedPizza is the first service in Japan that enables you to produce the world\'s number one popular dish, pizza, as a virtual 3D object from your smartphone at any time.', }, { property: 'og:type', content: 'website' }, { property: 'og:locale', content: 'ja_JP' }, { name: 'twitter:card', content: 'summary_large_image' }, { name: 'twitter:creator', content: '' }, { name: 'twitter:text:description', content: 'SpeedPizza is the first service in Japan that enables you to produce the world\'s number one popular dish, pizza, as a virtual 3D object from your smartphone at any time.', }, ], link: [ { rel: 'icon', type: 'image/x-icon', href: '/image/favicon.ico' }, { rel: 'preconnect', href: 'https://fonts.gstatic.com' }, { rel: 'stylesheet', href: 'https://fonts.googleapis.com/css2?family=Montserrat:ital,wght@0,300;0,500;0,600;0,700;1,600&display=swap', }, { rel: 'stylesheet', href: 'https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@100;300;400;500;700;900&display=swap', }, { rel: 'stylesheet', href: 'https://fonts.googleapis.com/css2?family=Roboto:wght@100;300;400;500;700;900&display=swap', }, ], // 全体で読み込ませたくない場合は、対象のpage内のheadで読み込ませよう script: [ { src: '/js/ammo.wasm.js', type: 'text/javascript', }, ], }, server: { port: 8000, }, publicPath: './', css: [{ src: '@/assets/sass/style.scss', lang: 'scss' }], components: true, modules: [ '@nuxtjs/style-resources', 'nuxt-svg-loader', ], styleResources: { scss: ['@/assets/sass/_variables.scss', '@/assets/sass/_mixin.scss'], }, build: { transpile: ['three'], babel: { presets({ isServer }) { return [ [ '@nuxt/babel-preset-app', isServer ? { targets: { node: 'current' }, } : { targets: { browsers: 'last 2 versions, ie >= 11' }, useBuiltIns: 'usage', corejs: 3, }, ], ]; }, }, extend(config, ctx) { config.node = { fs: 'empty', }; config.module.rules.push({ test: /\.(ogg|mp3|wav|mpe?g)$/i, loader: 'file-loader', options: { name: '[path][name].[ext]', }, }); }, terser: { terserOptions: { compress: { drop_console: process.env.NODE_ENV === 'production', }, }, }, }, rules: { 'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off', 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off', }, telemetry: false, render: { compressor: false, }, }; three.jsを使う時に筆者がよくやること three.jsなどで、3D周りをゴニョゴニョする際は、jsでのソース量がエグくなりがちなので、 canvasごと、component化しておいて、表示させたいページでそのcomponentを読み込むようにさせておく。 three.jsを使うときのCanvasComponentの基本ソース ThreeCanvas.vue <template> <canvas ref="canvas"></canvas> </template> <script type="module"> import * as THREE from 'three'; import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader.js'; import { MTLLoader } from 'three/examples/jsm/loaders/MTLLoader.js'; import { TrackballControls } from 'three/examples/jsm/controls/TrackballControls.js'; export default { props: {}, data() { return { canvas: null, renderer: null, controls: null, scene: null, camera: null, clock: null, }; }, async mounted() { await this.initCanvas(); this.animation(); }, methods: { async initCanvas() { console.log('init canvas'); // Firefox,SafariでcreateImageBitmapが使えない時のTips if ( !('createImageBitmap' in window) || /Firefox/.test(navigator.userAgent) === true ) { window.createImageBitmap = async function (blob) { return await new Promise((resolve, reject) => { const img = document.createElement('img'); img.addEventListener('load', function () { resolve(this); }); img.src = URL.createObjectURL(blob); }); }; } this.clock = new THREE.Clock(); this.canvas = this.$refs.canvas; // renderer 値は適当だよ、都度変えてね this.renderer = new THREE.WebGLRenderer({ antialias: true, canvas: this.canvas, autoClear: true, }); this.renderer.physicallyCorrectLights = true; this.renderer.setPixelRatio(this.windowPixelRatio()); this.renderer.setSize(window.innerWidth, window.innerHeight); this.renderer.toneMapping = THREE.LinearToneMapping; this.renderer.toneMappingExposure = 1.0; this.renderer.outputEncoding = THREE.sRGBEncoding; // scene this.scene = new THREE.Scene(); // camera 値は適当だよ、都度変えてね this.camera = new THREE.PerspectiveCamera( 58.715406782360475, window.innerWidth / window.innerHeight, 0.1, 10000 ); this.camera.aspect = window.innerWidth / window.innerHeight; this.camera.rotation.order = 'YXZ'; this.camera.position.x = 0; this.camera.position.y = 200; this.camera.position.z = 190; this.camera.rotation.x = 0; this.camera.rotation.y = 0; this.camera.rotation.z = 0; this.camera.up.x = 0; this.camera.up.y = 1; this.camera.up.z = 0; this.camera.updateProjectionMatrix(); // controller this.controls = new TrackballControls( this.camera, this.renderer.domElement ); // light 値は適当だよ、都度変えてね const dirLi = new THREE.DirectionalLight(0, 0.8); dirLi.color = { r: 0.95, g: 1.0, b: 0.85 }; dirLi.position.set(0, 0, 100); this.scene.add(dirLi); const amLi = new THREE.AmbientLight(0, 2.8); amLi.color = { r: 0.95, g: 1.0, b: 0.85 }; this.scene.add(amLi); // loadObjects // 後述 // resize this.onWindowResize(); window.addEventListener('resize', this.onWindowResize, false); // rendering this.render(); }, animation() { // animationがある場合は、frame animationになるようにrenderをループで呼び出す。1回renderingするだけの場合は呼び出さなくていい。 this.render(); requestAnimationFrame(this.animation); }, render() { if (!this.renderer.autoClear) { this.renderer.clear(); } if (this.controls) { this.controls.update(); } this.renderer.render(this.scene, this.camera); if (this.clock) { const delta = this.clock.getDelta(); } }, windowPixelRatio() { // 本来はwindowのpixelRatioを返すのが正しい、というか解像度が上がって綺麗に見える。(特にiOS/MacのRetinaディスプレイ) // return window.devicePixelRatio; // でも、筆者はあえて1を固定で返してます。理由としては、renderingが重くなるから。iOSのSafariとか重いとクラッシュしちゃう。。。 return 1; }, onWindowResize() { if (this.controls) { this.controls.handleResize(); } this.camera.aspect = window.innerWidth / window.innerHeight; this.camera.updateProjectionMatrix(); this.renderer.setPixelRatio(this.windowPixelRatio()); this.renderer.setSize(window.innerWidth, window.innerHeight); this.renderer.render(this.scene, this.camera); }, }, }; </script> <style lang="scss" scoped> canvas { display: inline-block; height: 100%; width: 100%; } </style> obj,mtl,textureファイルをloadしてobjectをsceneにadd 読み込ませるobj,mtl,textureファイルは、static/data/〜など静的ファイル内に置く three.jsにあるobj,mtl,textureのloadをmethod化(loadModel関数) initCanvas内で、loadModel関数を呼んで、sceneにadd とりあえず、pizza生地とピザ窯とトッピングのパイナップルのモデルを表示させてみた loadしたobjectをsceneにaddするソース ThreeCanvas.vue <script type="module"> ... 略 export default { ... 略 methods: { async initCanvas() { ... 略 // loadObjects // pizza 値は適当だよ、都度変えてね const pizza = await this.loadModel( 'base/dough_napoli.obj', 'base/dough_napoli.mtl', 'base/tex_dough_napoli.png' ); pizza.traverse((node) => { if (node.isMesh) { node.scale.x = 100; node.scale.y = 100; node.scale.z = 100; } }); pizza.position.set(0, 0, 0); this.scene.add(pizza); // oven 値は適当だよ、都度変えてね const oven = await this.loadModel( 'oven/stone_oven.obj', 'oven/stone_oven.mtl', 'oven/tex_stoneoven.png' ); oven.traverse((node) => { if (node.isMesh) { node.material.roughness = 1; node.material.metalness = 0; node.material.needsUpdate = true; node.scale.x = 150; node.scale.y = 150; node.scale.z = 150; } }); oven.position.set(0, 0, -500); this.scene.add(oven); // topping 値は適当だよ、都度変えてね const topping = await this.loadModel( 'toppings/pineapple/pineapple.obj', 'toppings/pineapple/pineapple.mtl', 'toppings/colortex.png' ); topping.traverse((node) => { if (node.isMesh) { node.scale.x = 300; node.scale.y = 300; node.scale.z = 300; } }); topping.position.set(0, 500, 0); this.scene.add(topping); ... 略 }, ... 略 async loadModel(obj, mtl, tex) { const base = this.$root.context.base; const path = base + 'data/models/obj/'; const materials = await new MTLLoader().setPath(path).loadAsync(mtl); materials.preload(); const object = await new OBJLoader() .setMaterials(materials) .setPath(path) .loadAsync(obj); const texture = await new THREE.TextureLoader() .setPath(path) .loadAsync(tex); object.traverse((node) => { if (node.isMesh) { node.castShadow = true; node.receiveShadow = true; node.material.map = texture; node.material.needsUpdate = true; } }); return object; }, }, }; </script> こんな感じ 物理エンジン(ammo.js)を加えてみる さて、ここからが本題。ammo.jsをどう使っていくのか、、、 PhysicsWorldのinit まずは、読み込んだammo.jsを使えるようにする。 mounted()の中で、initCanvas()を実行する前にやっておこう。 次に、PhysicsWorld(物理エンジンの世界)を作る。 これもmethod化(initPhysics)しておいて、initCanvasの前で実行しておく。 ThreeCanvas.vue <script type="module"> ... 略 export default { ... 略 data() { return { canvas: null, renderer: null, controls: null, scene: null, camera: null, clock: null, physicsWorld: null, transformAux1: null, softBodyHelpers: null, rigidBodies: [], softBodies: [], }; }, async mounted() { // 読み込んだammo.jsを使えるようにする Ammo = await Ammo(); // physics world(物理演算の世界)を作る this.initPhysics(); await this.initCanvas(); this.animation(); }, methods: { initPhysics() { const gravityConstant = -200; const collisionConfiguration = new Ammo.btSoftBodyRigidBodyCollisionConfiguration(); const dispatcher = new Ammo.btCollisionDispatcher(collisionConfiguration); const broadPhase = new Ammo.btDbvtBroadphase(); const solver = new Ammo.btSequentialImpulseConstraintSolver(); const softBodySolver = new Ammo.btDefaultSoftBodySolver(); this.physicsWorld = new Ammo.btSoftRigidDynamicsWorld( dispatcher, broadPhase, solver, collisionConfiguration, softBodySolver ); this.physicsWorld.setGravity(new Ammo.btVector3(0, gravityConstant, 0)); this.physicsWorld .getWorldInfo() .set_m_gravity(new Ammo.btVector3(0, gravityConstant, 0)); this.transformAux1 = new Ammo.btTransform(); this.softBodyHelpers = new Ammo.btSoftBodyHelpers(); }, ... 略 }, }; </script> addしたobjectからshape→bodyを作成してPhysicsWorldに追加 ここからは、筆者の解釈が入ってくるので、誤っていたらご指摘いただきたい。 ammo.jsでは、shape(形)を作って、そこに色々な設定(位置や回転、スケールなど)を行ってbody(物体)を作り、それをPhysicsWorldに追加する必要があると認識している。 そのshapeを作る際に、単純な球や直方体などは、ammo.jsの用意している関数で作成できるが、外部ファイルで読み込んだモデルに関しては、そうはいかない。 そこで、makeShape関数によって、読み込んだobjectのgeometryからshapeを形成することにした。 そして、そのshapeを使って、bodyを作成、physics Worldに追加をcreateRigidBody関数で行っている。 ThreeCanvas.vue <script type="module"> ... 略 export default { ... 略 methods: { async initCanvas() { ... 略 // loadObjects // pizza 値は適当だよ、都度変えてね const pizza = await this.loadModel( 'base/dough_napoli.obj', 'base/dough_napoli.mtl', 'base/tex_dough_napoli.png' ); pizza.traverse((node) => { if (node.isMesh) { node.scale.x = 100; node.scale.y = 100; node.scale.z = 100; } }); pizza.position.set(0, 0, 0); const pizzaShape = this.makeShape(pizza.children[0]); const pizzaPos = pizza.position; const pizzaQuat = pizza.children[0].quaternion; this.createRigidBody(pizza, pizzaShape, 0, pizzaPos, pizzaQuat); this.scene.add(pizza); ... 略 // topping 値は適当だよ、都度変えてね const topping = await this.loadModel( 'toppings/pineapple/pineapple.obj', 'toppings/pineapple/pineapple.mtl', 'toppings/colortex.png' ); topping.traverse((node) => { if (node.isMesh) { node.scale.x = 300; node.scale.y = 300; node.scale.z = 300; } }); topping.position.set(0, 500, 0); const toppingShape = this.makeShape(topping.children[0]); const toppingPos = topping.position; const toppingQuat = topping.children[0].quaternion; this.createRigidBody(topping, toppingShape, 15, toppingPos, toppingQuat); this.scene.add(topping); ... 略 }, ... 略 makeShape(mesh) { const triangleMesh = new Ammo.btTriangleMesh(true, true); triangleMesh.setScaling( new Ammo.btVector3(mesh.scale.x, mesh.scale.y, mesh.scale.z) ); const geometry = mesh.geometry; if (geometry instanceof THREE.BufferGeometry) { const vertexPositionArray = geometry.attributes.position.array; for (let i = 0; i * 3 < geometry.attributes.position.count; i++) { triangleMesh.addTriangle( new Ammo.btVector3( vertexPositionArray[i * 9], vertexPositionArray[i * 9 + 1], vertexPositionArray[i * 9 + 2] ), new Ammo.btVector3( vertexPositionArray[i * 9 + 3], vertexPositionArray[i * 9 + 4], vertexPositionArray[i * 9 + 5] ), new Ammo.btVector3( vertexPositionArray[i * 9 + 6], vertexPositionArray[i * 9 + 7], vertexPositionArray[i * 9 + 8] ), false ); } } else if (geometry instanceof THREE.Geometry) { for (let i = 0; i < geometry.faces.length; i++) { const face = geometry.faces[i]; if (face instanceof THREE.Face3) { const vec1 = new Ammo.btVector3(0, 0, 0); const vec2 = new Ammo.btVector3(0, 0, 0); const vec3 = new Ammo.btVector3(0, 0, 0); vec1.setX(face[0].x); vec1.setY(face[0].y); vec1.setZ(face[0].z); vec2.setX(face[1].x); vec2.setY(face[1].y); vec2.setZ(face[1].z); vec3.setX(face[2].x); vec3.setY(face[2].y); vec3.setZ(face[2].z); triangleMesh.addTriangle(vec1, vec2, vec3, true); } } } const shape = new Ammo.btBvhTriangleMeshShape(triangleMesh, true, true); return shape; }, createRigidBody(threeObject, physicsShape, mass, pos, quat) { physicsShape.setMargin(0.05); const transform = new Ammo.btTransform(); transform.setIdentity(); transform.setOrigin(new Ammo.btVector3(pos.x, pos.y, pos.z)); transform.setRotation( new Ammo.btQuaternion(quat.x, quat.y, quat.z, quat.w) ); const motionState = new Ammo.btDefaultMotionState(transform); const localInertia = new Ammo.btVector3(0, 0, 0); physicsShape.calculateLocalInertia(mass, localInertia); const rbInfo = new Ammo.btRigidBodyConstructionInfo( mass, motionState, physicsShape, localInertia ); const body = new Ammo.btRigidBody(rbInfo); threeObject.userData.physicsBody = body; this.rigidBodies.push(threeObject); body.setActivationState(4); this.physicsWorld.addRigidBody(body); return body; }, }, }; </script> renderの中で、PhysicsWorldの状態をupdateする これで終わりではなく、renderingする際に、PhysicsWorldの状態を常にupdateする必要がある。 render関数はアニメーション用にループで呼ばれるようになるので、updatePhysics関数をこの中で呼んであげる。 ThreeCanvas.vue <script type="module"> ... 略 export default { ... 略 render() { ... 略 if (this.clock) { const delta = this.clock.getDelta(); this.updatePhysics(delta); } }, ... 略 updatePhysics(deltaTime) { this.physicsWorld.stepSimulation(deltaTime, 10); for (let i = 0; i < this.rigidBodies.length; i++) { const objThree = this.rigidBodies[i]; const objPhys = objThree.userData.physicsBody; const ms = objPhys.getMotionState(); if (ms) { ms.getWorldTransform(this.transformAux1); const p = this.transformAux1.getOrigin(); const q = this.transformAux1.getRotation(); objThree.position.set(p.x(), p.y(), p.z()); objThree.quaternion.set(q.x(), q.y(), q.z(), q.w()); } } }, }, }; </script> さて、これでできたはず、見てみよう。 通り抜けた まとめ うまくいったこと loadしたobjectを自由落下させる うまくいかなかったこと loadしたobjectどうしの当たり判定(コリジョン設定) 次回 objectどうしの当たり判定を改善していく。 ちなみに エイプリルフールのネタ企画だったので、 これはこれで面白いということで、この時点でサービス公開した、、、orz
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Node-RED 1.3新機能紹介: Plugin framework

Node-REDのversion 1.3が2021年4月8日に公開されました(参照: Version 1.3 released, バージョン1.3がリリースされました)。 この記事では、Node-RED 1.3の新機能の一つである「Plugin framework」について紹介します。 Plugin frameworkとは? これまで、Node-REDに機能を追加する方法には下記の方法がありました。 Node-RED本体のコードを直接書き換える Node-REDのノードとして新機能を実装し、npmパッケージとしてインストールする 1つめの方法は一番自由度が高く任意の機能を追加可能となりますが、追加した機能を切り出して配布するのは困難です。またNode-RED本体がバージョンアップした際には、更新したコードを改めて新しいバージョンへと適合させる必要があります。 2つめの方法は機能を実装したコードがnpmパッケージとして分離されているため、機能の配布も容易になりますし、新しいバージョンへの追従も、APIの変更にのみ注意すればよいこととなり追従が容易になります。しかし、単にエディタ側に機能を追加したいときなどは、ノードのランタイム側の機能部として空のノード定義をしなければならないなど、不自然な記述が増えてしまいます。 Plugin frameworkでは、これを改善するために下記の特長を持っています。 - エディタ側・ランタイム側のうち、拡張が必要な側のみのコードを記述すればよい。 - プラグインから他のプラグインの機能を利用することができ、プラグイン自体を拡張可能になる。 - プラグイン内の特定のフォルダにいれたファイルを、エディタ側からHTTP APIを介して読み込むことができるため、任意の画像ファイルやJavascriptライブラリをエディタ側に追加するのが容易になる。 Plugin frameworkの機能の詳細 Pluginは、npmパッケージの形で実装します。フォルダの構造は下記のようになります。 |- package.json |- plugin.js |- plugin.html \- resources |- resourcefile.js package.json package.jsonには、通常のnpmパッケージとして記述する項目以外に、このモジュールがNode-REDのプラグインであると認識させるための下記の情報を記述します。 "node-red": { "plugins": { "sampleplugin": "plugin.js" } } pluginsの値となるオブジェクトは、キーとしてプラグインの名前(この例ではsampleplugin)、対応する値としてプラグインを実装しているファイル(この例ではplugin.js)を持ちます。もし、エディタ側のみに拡張が必要な時には.htmlファイルを指定します。 ランタイム側の拡張 ランタイム側のコードでは、プラグインを登録するためにRED.plugins.registerPlugin()を実行します。 module.exports = function(RED) { // ... RED.plugins.registerPlugin("sample-runtime-plugin-identifier", { type: "sample-runtime-plugin-type", onadd: function() { // プラグインが登録されるときに実行される }, onremove: function() { // プラグインが登録解除される時に実行される }, settings: { // $HOME/.node-red/settings.js から読み込む設定項目と、エディタ側に公開するかのフラグ sampleSetting: { value: "sample", exportable: true } } }); } 上記のコードでは、プラグインの識別子およびタイプの登録と、プラグインが登録/登録解除されたときに実行される関数、およびsettings.jsから読み込む設定項目を指示しています。 これにより、ランタイムにプラグインが組み込まれ、RED.plugins.getPluginsList()などのランタイムAPIから プラグインが参照できるようになります。通常、onaddで定義した関数内で各種イベントに対するハンドラを登録することがプラグイン実装の中心になります。 エディタ側の拡張 エディタ側にもほぼ同様の枠組みが用意されています。ただし、settings.js内の設定項目の参照は ランタイム側で設定するためエディタ側のコードでは設定がありません。 <script type="text/javascript"> //... RED.plugins.registerPlugin("sample-editor-plugin-identifier", { type: "sample-editor-plugin-type", onadd: function() { /* ... */ }, onremove: function() { /* ... */ } }); </script> ランタイムと同様、上記APIの呼び出し以降はRED.plugins.getPluginsList()などでプラグインを参照することが できます。 Resources ディレクトリ エディタ側の機能拡張では、追加の画像ファイルやHTML/CSSファイルを用意することが多くなります。 ノードのプラグインでは、これらのファイルを読み込むためには独自でAPIエンドポイントを用意し、 そのエンドポイントを使ってファイルをエディタ側に読み込むことが必要でした。 Plugin frameworkでは、パッケージのresourcesフォルダに格納したファイルが /resources/<プラグインパッケージの名前>/<resourceフォルダ内のファイル名>として見えるようになります。 これにより、エディタ側のHTMLファイルで <img src="/resources/plugin-package-name/foo.svg">と記述することでresources/foo.svgを読み込めるようになります。なお、エディタ/ランタイムでRED.plugins.registerPlugin()を実行しなくても、package.jsonにnode-red.pluginsを記述すれば公開されます。 なお、Plugin frameworkの仕様の詳細は、下記デザインノートに書かれています(注: 2021年4月27日現在、Pull Requestがマージされていないため見つけづらいです。) Node-RED Plugins Plugin frameworkをつかった機能追加の実例 ここでは、「Node-REDのフローエディタのメニューに新たな項目"Greetings"を作り、それを選ぶとアラートに"Hello, plugin"と表示されるようにする」というシンプルなプラグインを作成します。 フォルダの構成は下記の通りになります。 |- package.json |- greetings.html \- resources |- hello.png まず、package.jsonを記述します。 { "name": "node-red-contrib-plugin-greetings", "version": "1.0.0", "description": "add greeting menu item", "author": "Your Name", "license": "Apache-2.0", "node-red": { "plugins": { "greeting": "greeting.html" } } } つぎに、プラグインの本体となるgreeting.htmlを作成します。 <script type="text/javascript"> RED.plugins.registerPlugin("greeting-menu", { type: "greeting-menu", onadd: function() { RED.actions.add("greeting:selected", function() { RED.notify('<p><img src="/resources/node-red-contrib-plugin-greeting/hello.png">Hello, plugin!</p>', "success"); }); RED.menu.addItem("red-ui-header-button-sidemenu", { id: "menu-item-greeting", label: "Greeting", onselect: "greeting:selected" }); } }); </script> ここでは、RED.plugins.registerPlugin()でプラグインを登録し、登録完了時のコールバック関数onadd内で メニューを選んだ時に表示される内容の設定(RED.actions.add())と、 メニューへの項目の追加(RED.menu.addItem())を実行しています。 最後に、通知に表示される画像ファイルをresources/hello.pngに配置します。 これでnpmパッケージが出来上がりましたので、Node-REDのユーザディレクトリ(通常はホームディレクトリの.node-red)にインストールします。(<...>/node-red-contrig-plugin-greetingには上記パッケージ一式を格納したフォルダのパスが入ります) % cd ~/.node-red % npm install <...>/node-red-contrib-plugin-greeting それでは実行してみましょう。 メニュー最下部に"Greeting"という項目ができているのがわかります。 ここで"Greeting"を選ぶと、下記のように"Hello, plugin!"とかかれた通知画面が表示されます。 ごく簡単な機能ですが、Node-RED本体のコードに手を入れることなくエディタの機能が拡張できることが理解できたのではないでしょうか。 おわりに これまではNode-REDの拡張には、Node-REDのコアを注意深く改造していく必要がありましたが、このPlugin frameworkやRuntime Editor Split、Pluggable Message Routingなどの形で拡張機能が比較的容易に実装できるようになってきました。Pluginの形で革新的な機能が導入され、成熟した時点でNode-REDに取り込まれる、という形でNode-REDの機能がコミュニティの手で磨かれていく流れができることが楽しみです。 Node-RED 1.3新機能紹介 Node-RED1.3の新機能: Plugin framework (本記事) Node-RED1.3の新機能: Functionノード Node-RED1.3の新機能: サブフローのノード化
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Node-RED 1.3の新機能: Plugin framework

Node-REDのversion 1.3が2021年4月8日に公開されました(参照: Version 1.3 released, バージョン1.3がリリースされました)。 この記事では、Node-RED 1.3の新機能の一つである「Plugin framework」について紹介します。 Plugin frameworkとは? これまで、Node-REDに機能を追加する方法には下記の方法がありました。 Node-RED本体のコードを直接書き換える Node-REDのノードとして新機能を実装し、npmパッケージとしてインストールする 1つめの方法は一番自由度が高く任意の機能を追加可能となりますが、追加した機能を切り出して配布するのは困難です。またNode-RED本体がバージョンアップした際には、更新したコードを改めて新しいバージョンへと適合させる必要があります。 2つめの方法は機能を実装したコードがnpmパッケージとして分離されているため、機能の配布も容易になりますし、新しいバージョンへの追従も、APIの変更にのみ注意すればよいこととなり追従が容易になります。しかし、単にエディタ側に機能を追加したいときなどは、ノードのランタイム側の機能部として空のノード定義をしなければならないなど、不自然な記述が増えてしまいます。 Plugin frameworkでは、これを改善するために下記の特長を持っています。 - エディタ側・ランタイム側のうち、拡張が必要な側のみのコードを記述すればよい。 - プラグインから他のプラグインの機能を利用することができ、プラグイン自体を拡張可能になる。 - プラグイン内の特定のフォルダにいれたファイルを、エディタ側からHTTP APIを介して読み込むことができるため、任意の画像ファイルやJavascriptライブラリをエディタ側に追加するのが容易になる。 Plugin frameworkの機能の詳細 Pluginは、npmパッケージの形で実装します。フォルダの構造は下記のようになります。 |- package.json |- plugin.js |- plugin.html \- resources |- resourcefile.js package.json package.jsonには、通常のnpmパッケージとして記述する項目以外に、このモジュールがNode-REDのプラグインであると認識させるための下記の情報を記述します。 "node-red": { "plugins": { "sampleplugin": "plugin.js" } } pluginsの値となるオブジェクトは、キーとしてプラグインの名前(この例ではsampleplugin)、対応する値としてプラグインを実装しているファイル(この例ではplugin.js)を持ちます。もし、エディタ側のみに拡張が必要な時には.htmlファイルを指定します。 ランタイム側の拡張 ランタイム側のコードでは、プラグインを登録するためにRED.plugins.registerPlugin()を実行します。 module.exports = function(RED) { // ... RED.plugins.registerPlugin("sample-runtime-plugin-identifier", { type: "sample-runtime-plugin-type", onadd: function() { // プラグインが登録されるときに実行される }, onremove: function() { // プラグインが登録解除される時に実行される }, settings: { // $HOME/.node-red/settings.js から読み込む設定項目と、エディタ側に公開するかのフラグ sampleSetting: { value: "sample", exportable: true } } }); } 上記のコードでは、プラグインの識別子およびタイプの登録と、プラグインが登録/登録解除されたときに実行される関数、およびsettings.jsから読み込む設定項目を指示しています。 これにより、ランタイムにプラグインが組み込まれ、RED.plugins.getPluginsList()などのランタイムAPIから プラグインが参照できるようになります。通常、onaddで定義した関数内で各種イベントに対するハンドラを登録することがプラグイン実装の中心になります。 エディタ側の拡張 エディタ側にもほぼ同様の枠組みが用意されています。ただし、settings.js内の設定項目の参照は ランタイム側で設定するためエディタ側のコードでは設定がありません。 <script type="text/javascript"> //... RED.plugins.registerPlugin("sample-editor-plugin-identifier", { type: "sample-editor-plugin-type", onadd: function() { /* ... */ }, onremove: function() { /* ... */ } }); </script> ランタイムと同様、上記APIの呼び出し以降はRED.plugins.getPluginsList()などでプラグインを参照することが できます。 Resources ディレクトリ エディタ側の機能拡張では、追加の画像ファイルやHTML/CSSファイルを用意することが多くなります。 ノードのプラグインでは、これらのファイルを読み込むためには独自でAPIエンドポイントを用意し、 そのエンドポイントを使ってファイルをエディタ側に読み込むことが必要でした。 Plugin frameworkでは、パッケージのresourcesフォルダに格納したファイルが /resources/<プラグインパッケージの名前>/<resourceフォルダ内のファイル名>として見えるようになります。 これにより、エディタ側のHTMLファイルで <img src="/resources/plugin-package-name/foo.svg">と記述することでresources/foo.svgを読み込めるようになります。なお、エディタ/ランタイムでRED.plugins.registerPlugin()を実行しなくても、package.jsonにnode-red.pluginsを記述すれば公開されます。 なお、Plugin frameworkの仕様の詳細は、下記デザインノートに書かれています(注: 2021年4月27日現在、Pull Requestがマージされていないため見つけづらいです。) Node-RED Plugins Plugin frameworkをつかった機能追加の実例 ここでは、「Node-REDのフローエディタのメニューに新たな項目"Greetings"を作り、それを選ぶとアラートに"Hello, plugin"と表示されるようにする」というシンプルなプラグインを作成します。 フォルダの構成は下記の通りになります。 |- package.json |- greetings.html \- resources |- hello.png まず、package.jsonを記述します。 { "name": "node-red-contrib-plugin-greetings", "version": "1.0.0", "description": "add greeting menu item", "author": "Your Name", "license": "Apache-2.0", "node-red": { "plugins": { "greeting": "greeting.html" } } } つぎに、プラグインの本体となるgreeting.htmlを作成します。 <script type="text/javascript"> RED.plugins.registerPlugin("greeting-menu", { type: "greeting-menu", onadd: function() { RED.actions.add("greeting:selected", function() { RED.notify('<p><img src="/resources/node-red-contrib-plugin-greeting/hello.png">Hello, plugin!</p>', "success"); }); RED.menu.addItem("red-ui-header-button-sidemenu", { id: "menu-item-greeting", label: "Greeting", onselect: "greeting:selected" }); } }); </script> ここでは、RED.plugins.registerPlugin()でプラグインを登録し、登録完了時のコールバック関数onadd内で メニューを選んだ時に表示される内容の設定(RED.actions.add())と、 メニューへの項目の追加(RED.menu.addItem())を実行しています。 最後に、通知に表示される画像ファイルをresources/hello.pngに配置します。 これでnpmパッケージが出来上がりましたので、Node-REDのユーザディレクトリ(通常はホームディレクトリの.node-red)にインストールします。(<...>/node-red-contrig-plugin-greetingには上記パッケージ一式を格納したフォルダのパスが入ります) % cd ~/.node-red % npm install <...>/node-red-contrib-plugin-greeting それでは実行してみましょう。 メニュー最下部に"Greeting"という項目ができているのがわかります。 ここで"Greeting"を選ぶと、下記のように"Hello, plugin!"とかかれた通知画面が表示されます。 ごく簡単な機能ですが、Node-RED本体のコードに手を入れることなくエディタの機能が拡張できることが理解できたのではないでしょうか。 おわりに これまではNode-REDの拡張には、Node-REDのコアを注意深く改造していく必要がありましたが、このPlugin frameworkやRuntime Editor Split、Pluggable Message Routingなどの形で拡張機能が比較的容易に実装できるようになってきました。Pluginの形で革新的な機能が導入され、成熟した時点でNode-REDに取り込まれる、という形でNode-REDの機能がコミュニティの手で磨かれていく流れができることが楽しみです。 Node-RED 1.3新機能紹介 Node-RED1.3の新機能: Plugin framework (本記事) Node-RED1.3の新機能: Functionノード Node-RED1.3の新機能: サブフローのノード化
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

日付の取得

概要 日付と税込み価格をアラートで表示 日付を取得 index.js function date (){ const dd = new Date; const year = dd.getFullYear(); const month = dd.getMonth() +1; const d = dd.getDate(); const mon = [year,month,d]; console.log(mon); return mon; } ①ddに現在日時を保存 ②現在日時をそれぞれyear,month,d,にメソッドで変更します。  *Monthは海外では文字列(sundeyとか)ですが、日本では1~12月で表されます。その都合で、0始まりになっています。なので、呼び出すときは、+1をします。1を足し忘れると−1月で表示されました。 ③それらデータをまとめてリターンするので、配列にします。 アラートで表示 index.js    const m = date(); const sub = totalTax(); const win =[m,sub]; window.alert(win); そして今回の目的は、taxと日時をアラートで出したいので、配列にします。 配列 データを一列に並べたものです。 配列として1つの定数に保存することで、大量のデータを扱うことができます。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Java Script学習(ES6) 基礎編 ~forEachメソッドとは?~

こちらではJavaScript学習の備忘録を記載しております。 プログラミング初心者、関心をお持ちの方々の参考になれば幸いです。 forEachメソッドとは? 配列の中の要素を1つずつ取り出して、すべての要素に繰り返し同じ処理を行うメソッド。 script.js 配列 = [要素1, 要素2, 要素3]; 配列.forEach((引数) => {処理}); // 配列の中の要素を1つずつ取り出し、同じ処理を行う 上記メソッドの処理の流れ ①要素1が引数へ代入され、要素1に対しての処理が行われる ②要素2が引数へ代入され、要素2に対しての処理が行われる ③要素3が引数へ代入され、要素3に対しての処理が行われる forEachメソッドの使い方 script.js const nembers = [1, 2, 3]; nembers.forEach((nember) => {console.log(nember); }); // 出力結果 1 2 3 また、上記引数に入っている関数 (nember) => {console.log(nember);} をコールバック関数と呼ぶ。 長いコードの書き方 forEachメソッドのように、引数にコールバック関数を使うメソッドはコードが長くなりやすいため、以下のように書くことが多い。 処理前の中括弧「 { 」から改行することで、長いコードでも見やすくなるという利点がある。 script.js const nembers = [1, 2, 3]; nembers.forEach((nember) => { console.log(nember); }); おわりに 本当に最初の基本的な部分であります。 ご指摘あれば、よろしくお願い致します!!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScript学習(ES6) 基礎編 ~forEachメソッドとは?~

こちらではJavaScript学習の備忘録を記載しております。 プログラミング初心者、関心をお持ちの方々の参考になれば幸いです。 forEachメソッドとは? 配列の中の要素を1つずつ取り出して、すべての要素に繰り返し同じ処理を行うメソッド。 script.js 配列 = [要素1, 要素2, 要素3]; 配列.forEach((引数) => {処理}); // 配列の中の要素を1つずつ取り出し、同じ処理を行う 上記メソッドの処理の流れ ①要素1が引数へ代入され、要素1に対しての処理が行われる ②要素2が引数へ代入され、要素2に対しての処理が行われる ③要素3が引数へ代入され、要素3に対しての処理が行われる forEachメソッドの使い方 script.js const nembers = [1, 2, 3]; nembers.forEach((nember) => {console.log(nember); }); // 出力結果 1 2 3 また、上記引数に入っている関数 (nember) => {console.log(nember);} をコールバック関数と呼ぶ。 長いコードの書き方 forEachメソッドのように、引数にコールバック関数を使うメソッドはコードが長くなりやすいため、以下のように書くことが多い。 処理前の中括弧「 { 」から改行することで、長いコードでも見やすくなるという利点がある。 script.js const nembers = [1, 2, 3]; nembers.forEach((nember) => { console.log(nember); }); おわりに 本当に最初の基本的な部分であります。 ご指摘あれば、よろしくお願い致します!!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

DOMの操作

概要 inputから値を受け取り、要素を生成します。 HTML index.html <input type="text"> <button>Add</button>    <ul> </ul> 今回はこちらのHTTMLから情報を受け取り、ul要素を生成して行きます。 inputからの入力内容をul要素に反映していきます。 JS index.js { const add = document.querySelector('button'); add.addEventListener('click',e => { const li = document.createElement('li'); const text = document.querySelector('input'); li.textContent = text.value; document.querySelector('ul').appendChild(li); }); } ①定数addにquerySerectorでボタン要素を代入します。 ②add.addEventListenerでクリックイベントが起きたときの関数を登録します。 ③今回作成する定数liに要素の作成を代入します。 今回はul要素にを作成します。document.createElement(タグ名);でDOM内のツリーに追加することができます。 ③input要素から値を受け取るために、document.querySelector('input');で要素を取得。定数textに代入します。 ④li要素のvalue属性を取得します。 ⑤appendChild(li);で要素の追加を行います。 *removeChild();でノードをとりのぞきます。 あくまで、JS側で処理しているのはDOMでありHTMLファイルが書き換えられているわけではありません。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

kintoneのサブテーブルの行を入れ替える技

今回は、サブテーブルの行入れ替えをするカスタマイズです。 ※DOM操作をするカスタマイズです。kintoneの仕様変更などで使えなくなる可能性があります。また、サブテーブルは1つだけにしておいてください? ↓できあがりはこんな感じになります。 アプリの準備 下図のようなサブテーブルを作ります。 フィールド種類 フィールドコード 備考 テーブル テーブル ルックアップ ルックアップ ルックアップ設定をお忘れなく 文字列(1行) 文字 ルックアップ設定、「他のフィールドのコピー」で指定 文字列(1行) メモ ※ルックアップ呼び出し元は何でも良いので適当なマスタアプリを作ってください。 JavaScript まず、JavaScript / CSSでカスタマイズに Cybozu CDN のページから jQuery と jQuery UI のURLをコピペしてに追加しておきます。 jQuery: https://js.cybozu.com/jquery/3.6.0/jquery.min.js jQueryUI: https://js.cybozu.com/jqueryui/1.12.1/jquery-ui.min.js 自分で書くコード 行入れ替えのしくみですが、 1. 追加編集画面を開いたときにDOM操作で、サブテーブルの行タグに、上から0, 1, 2, ...となるidを付与します。 2. 並び替えは後に一旦表示上のフィールド値を保存(kintone.app.record.set(obj))して、 3. フィールド値変更や、行の追加削除イベントでは変更した値を保存しておきます。(resultTableValues) 4. 次に並び替えが発生したときはresultTableValuesを並び替えてkintone.app.record.set(obj)します。 コレで行の入れ替えができます。 JavaScript / CSSでカスタマイズに追加するのをお忘れなく! "use strict"; (() => { "use strict"; // 編集イベントが発生するたびに更新内容を入れとくもの let resultTableValues = []; // DOM操作。<tbody>と<tr>にidをつける const setRowNum = () => { let st = document.querySelector("tbody"); st.id = "st"; st.rows.forEach((r, idx) => { r.id = idx; }); }; // 追加/編集画面 kintone.events.on(["app.record.create.show", "app.record.edit.show"], (event) => { setRowNum(); // 初期値は最初のレコード情報 resultTableValues = event.record.テーブル.value; // ドラッグ&ドロップで入れ替えたら動く $("#st").sortable({ update: (event, ui) => { const tmpRows = []; // 並び替え後のソート順のとおりに、resultTableValuesの行をtmpRowsにpush入れる $("#st").sortable("toArray").forEach((element) => { // ルックアップの取得を忘れずに resultTableValues[Number(element)].value.ルックアップ.lookup = true; // 並び替え後のテーブル作成 tmpRows.push(resultTableValues[Number(element)]); }); // 完成した並び替え後のテーブル(tmpRows)をresulutTableValuesに入れ直す resultTableValues = tmpRows; // 画面表示中のレコードの値を更新する const r = kintone.app.record.get(); // 一回クリアしないとゴミが残る?ようなので一旦クリアする r.record.テーブル.value = []; kintone.app.record.set(r); // 画面表示中レコードを並び替え後のテーブルで更新する r.record.テーブル.value = resultTableValues; kintone.app.record.set(r); // 並び替えたらDOM操作でid振り直す setRowNum(); }, }); return event; }); // サブテーブルの行追加&削除 kintone.events.on(["app.record.create.change.テーブル", "app.record.edit.change.テーブル"], (event) => { // <tr>に番号をふりなおす setRowNum(); // 行追加&削除したテーブルでresultTableValuesを更新しておく resultTableValues = event.record.テーブル.value; return event; }); // サブテーブル内フィールド値更新 kintone.events.on([ "app.record.create.change.メモ", "app.record.edit.change.メモ", "app.record.create.change.文字", "app.record.edit.change.文字", ], (event) => { // resultTableValuesをフィールド値更新後の内容にする resultTableValues = event.record.テーブル.value; return event; }); })(); まとめ ドラッグ&ドロップで行の入れ替えができると色々嬉しいかも知れません。 元々ある部品をDOM操作するのでそんなにオススメできる技ではないのですが、是非試してみてね✨✨
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む