HTML のフォーム送信データを書き換える2021年12月18日 22時28分

この記事は HTML アドベントカレンダーの 18 日目の分、兼 JavaScript アドベントカレンダーの 18 日目の分です。


HTML のフォーム送信処理が実行されるときに、JavaScript を使って送信データを書き換えるにはどうしたらよいでしょうか。まず思いつくのが submit イベント発生時にフォームコントロール要素の入力値 (value プロパティの値など) を書き換えることでしょう。別の手段として formdata イベントがあります。formdata イベントを利用すれば、フォームコントロール要素に手を加えずに送信データを書き換えることができます。

formdata イベントに紐づく FormDataEvent オブジェクトformData プロパティを持ち、その値はまさに今送信されようとしているデータを表した FormData オブジェクトとなっています。この FormData オブジェクトに対して appendsetdelete メソッドを呼び出すことで、送信されるデータを変更することができるのです。

以下の例では、フォームを送信する際に time というキーに対して現在日時を表す文字列が設定され、/search?q=keyword&time=2021-12-18T12%3A00%3A00.000Z といった URL に移動します。

<form action="/search"
      onformdata="event.formData.append('time', new Date().toISOString());">
  <p>
    <input type="search" name="q" aria-label="検索する語">
    <button type="submit">検索する</button>
  </p>
</form>

formdata イベントは、form 要素オブジェクトを引数にして FormData コンストラクタを呼び出したとき (new FormData(form)) にも (引数として渡された form 要素上で) 発生します。

HTML のフォーム送信に使われたボタンを判別する2021年12月16日 23時00分

この記事は HTML アドベントカレンダーの 16 日目の分、兼 JavaScript アドベントカレンダーの 16 日目の分です。


HTML のフォームには複数の送信ボタンを設置できます。ある送信ボタンを実行してフォームを送信すると、その送信ボタンの name 属性と (送信ボタンが button 要素なら) value 属性の値も送信データに含まれます。

どの送信ボタンを実行してフォームを送信したのか、JavaScript からは submit イベントに紐づく SubmitEvent オブジェクトsubmitter プロパティで判別できます。以下の例では、「公開する」または「削除する」ボタンを実行すると、JavaScript コンソールにそのボタンが出力されます。

<form
    method="post"
    action="/edit"
    onsubmit="console.log(event.submitter); return false;">
  <p>
    <button type="submit" name="publish" value="1">公開する</button>
    <button type="submit" name="delete" value="1">削除する</button>
  </p>
</form>

submitter プロパティが実装される前は、各ボタンの click イベントを基に、どのボタンが最後に実行されたのか記憶しておく必要がありました。そこからするとずいぶん楽になりましたね。

HTML 要素の innerText プロパティで要素が生成されうる2021年12月10日 11時13分

この記事は HTML アドベントカレンダーの 10 日目の分、兼 JavaScript アドベントカレンダーの 10 日目の分です。


HTML 標準では、HTML 文書を JavaScript などのプログラミング言語から扱うためのインターフェイスも定義しています。そのひとつが HTML 要素の innerText プロパティ、要素の内容を文字列として取得・設定するプロパティです。かつて Internet Explorer が独自に実装し、他の Web ブラウザも追従した結果として標準化されたものですね。

innerText プロパティの値の取得

innerText プロパティで取得できる値は、ブラウザにレンダリングされたようなテキスト内容となります。CSS で display: none; が指定された要素の内容は含まれませんし、display: block; が指定された要素の内容の前後には改行文字 (U+000A) が挿入されます。表のセル同士の間にはタブ文字 (U+0009) が挿入されます。

そのため、innerText プロパティの値を取得する際にはブラウザの描画処理、いわゆるリフローが走ることになります。スタイルを気にせず単に要素のテキスト内容を取得したいときは、innerText プロパティではなく textContent プロパティを使ったほうがよいでしょう。textContent プロパティの値の取得時にはリフローが走りません。

innerText プロパティの値の設定

innerText という名前からすると値の設定時には単なるテキストノードが生成されそうな気もしますが、HTML 要素が生成されることもあります。設定する値に改行文字が含まれるときは、その改行文字が br 要素に置換されるのです。

単に要素のテキスト内容を設定したいときは、innerText プロパティではなく textContent プロパティを使ったほうがよいでしょう。textContent プロパティの値の設定時には HTML 要素が生成されません。

参考文献

JavaScript の正規表現で複数文字からなる絵文字を扱えるようにする提案2021年12月04日 23時31分

この記事は JavaScript アドベントカレンダーの 4 日目の分です。


今やあちこちで使われている絵文字ですが、その中には 1 文字に見えるのに複数の文字 (符号位置) から構成されるものがあります。例えば「👨‍👩」という男女が並んだ絵文字は、U+1F468 MAN、U+200D ZERO WIDTH JOINER、U+1F469 WOMAN の 3 つの符号位置からなります。

JavaScript の正規表現でこのような絵文字にマッチさせようとすると正規表現パターンが長大になってしまいます。また、文字クラス [...] 内にこのような絵文字を記述すると、「U+1F468、U+200D、U+1F469 という符号位置の並び」ではなく「U+1F468、U+200D、U+1F469 のいずれかの符号位置」にマッチしてしまいます (/u フラグが有効な場合)。

これを解決するための提案が ECMAScript proposal: support properties of strings (a.k.a. “sequence properties”) in Unicode property escapes です。Unicode において符号位置の並びに対して定義されているプロパティを、正規表現パターンで扱えるようにしようというもので、2021 年 12 月現在は stage 2 となっています。

これが実現すれば、\p{RGI_Emoji} という正規表現パターンが「👨‍👩」という符号位置の並びにマッチするようになります。さらに、このパターンは文字クラス [...] の内部で使うこともできます。ひとつの文字クラスにマッチするのはひとつの符号位置 (/u フラグが無効な場合はひとつの符号単位) というこれまでの常識が覆されるのです。

個人的には、将来的に JavaScript 組み込みの機能で Unicode の書記素クラスタを扱えるようにならないかと期待しています。

参考文献

Node.js ではイベントから非同期イテレータを生成できる2021年12月02日 23時37分

この記事は JavaScript アドベントカレンダーの 2 日目の分です。


Node.js の Events モジュールでは、events.on メソッドを提供しています (EventEmitter オブジェクトの on メソッドとは別物です)。これは、イベントが列挙されるような非同期イテレータを生成するものです。第 1 引数にイベントを発生させる EventEmitter オブジェクトまたは EventTarget オブジェクトを、第 2 引数に列挙したいイベントの名前を指定します。

以下の例では、実行すると 1 秒後と 3 秒後にそれぞれ「foo イベントが発生しました」と出力されます。

// events-on.mjs

import { on } from 'events';

const target = new EventTarget();
setTimeout(() => {
  target.dispatchEvent(new Event('foo'));
}, 1000);
setTimeout(() => {
  target.dispatchEvent(new Event('foo'));
}, 3000);

(async () => {
  for await (const [event] of on(target, 'foo')) {
      console.log(`${event.type} イベントが発生しました。`)
  }
})();

RxJS の Observable.fromEvent メソッドと機能が似ていますね。

ブラウザ上でも使えると面白い書き方ができるのかもしれませんが、Events モジュールのブラウザ用実装では events.on メソッドに対応していません。issue は立っており、作業ブランチも存在するものの、半年以上動きがなさそうです。