HTML のフォームコントロール要素と label 要素の紐づけ ― 2021年12月24日 21時11分
この記事は HTML アドベントカレンダーの 24 日目の分、兼 JavaScript アドベントカレンダーの 24 日目の分です。
HTML のフォームコントロール要素 (input
、textarea
、select
、button
要素など) には、label
要素を使ってラベルを指定できます。ここでいうラベルとは、そのフォームコントロールに何を入力するか・そのフォームコントロールで何ができるのかの簡単な説明であり、人間が読んで理解できるようなフォームコントロールの名前です。
ある label
要素の子孫にフォームコントロール要素が存在すれば、その label
要素の内容が、そのフォームコントロール要素のラベルとなります。そうでない場合、label
要素の for
属性にフォームコントロール要素の ID (id
属性の値) を指定する必要があり、その label
要素の内容が、その ID を持つフォームコントロール要素のラベルとなります。
このフォームコントロール要素と label
要素との紐づきは JavaScript を使って参照できます。フォームコントロール要素オブジェクトの labels
プロパティはそのフォームコントロール要素と紐づく label
要素の一覧 (NodeList
オブジェクト) を返し、label
要素オブジェクト (HTMLLabelElement
オブジェクト) の control
プロパティはその label
要素に紐づくフォームコントロール要素を返します。
labels
プロパティの名前が複数形なのは、ひとつのフォームコントロール要素に対して複数の label
要素を紐づけられるからですね。
<label id="query-label-1" for="query-field">キーワード</label>
<label id="query-label-2" for="query-field">URL</label>
<input id="query-field" type="search" name="q">
const label1 = document.getElementById('query-label-1');
const label2 = document.getElementById('query-label-2');
const field = document.getElementById('query-field');
console.assert(label1.control === field, 'control プロパティでフォームコントロールを参照できる (1)');
console.assert(label2.control === field, 'control プロパティでフォームコントロールを参照できる (2)');
console.assert(field.labels[0] === label1, 'labels プロパティで label 要素を参照できる (1)');
console.assert(field.labels[1] === label2, 'labels プロパティで label 要素を参照できる (2)');
あるフォームコントロールにおいて、labels
プロパティの返す NodeList
オブジェクトは常に同一です。紐づく label
要素に変更があれば、その NodeList
オブジェクトの内容が動的に変化します。
const oldLabels = field.labels;
label1.remove();
const newLabels = field.labels;
console.assert(oldLabels === newLabels, 'labels プロパティの値は何度参照しても同一のオブジェクトである');
console.assert(oldLabels.length === 1, 'labels プロパティの値は動的に変化する');
ただし、実際のところひとつのフォームコントロール要素に複数の label
要素を紐づけるような場面はほとんどないと思います。
button
要素にも label
要素を紐づけられます。しかしながら、button
要素の場合は自身の内容がラベルとして扱われるので (<button type="submit">検索する</button>
なら「検索する」がそのボタンのラベルになります)、実際のところ button
要素に label
要素を紐づけるような場面はほとんどないと思います。
ラベルは aria-label
属性や aria-labelledby
属性を使って指定することもできます。
<form action="/search">
<p>
<input type="search" name="q" aria-label="キーワード">
<button type="submit" aria-label="検索する">🔍</button>
</p>
</form>
HTML のテキスト入力欄の入力値の一部を置換する ― 2021年12月23日 23時48分
この記事は HTML アドベントカレンダーの 23 日目の分、兼 JavaScript アドベントカレンダーの 23 日目の分です。
HTML のテキスト入力欄 (<input type="text">
要素や textarea
要素など) で選択範囲の文字列を置換したいとき、一昔前は JavaScript で以下のように書く必要がありました。
// 引数 field には HTMLInputElement オブジェクトまたは HTMLTextAreaElement オブジェクトを、
// 引数 newText には文字列を、それぞれ受け取る。
function replaceSelectionText(field, newText) {
var start = field.selectionStart;
var end = field.selectionEnd;
var text = field.value;
field.value = text.substring(0, start) + newText + text.substring(end);
field.setSelectionRange(start, start + newText.length);
}
(さらに大昔は IE 向けに TextRange
オブジェクトを使う必要がありました。)
今は HTML 標準で setRangeText
メソッドが定義されているため、以下のように書けます。
function replaceSelectionText(field, newText) {
field.setRangeText(newText);
}
setRangeText
メソッドには置換する範囲を指定することもできます。テキスト入力欄 field
の入力値が abcde
のとき、以下のコードを実行すると入力値が axde
になります。
field.setRangeText('x', 1, 3);
このとき、元の入力値において改行は \n
に正規化され、1 文字として数えられます。また、「1 文字」というのが Unicode の符号位置ではなく UTF-16 の符号単位を表すことに注意が必要です。
置換する範囲を指定して setRangeText
メソッドを呼び出したとき、選択範囲はメソッド呼び出しの前後であまり変化しないように調整されます。この挙動は、第 4 引数に select
、start
、end
、preserve
のいずれかの値を指定することで変更できます (省略時は preserve
)。以下の例ではメソッド呼び出し後に部分文字列 x
が選択されます。
field.setRangeText('x', 1, 3, 'select');
Web Platform Tests での HTML 標準のテストに触れる ― 2021年12月22日 23時50分
この記事は HTML アドベントカレンダーの 22 日目の分、兼 JavaScript アドベントカレンダーの 22 日目の分です。
Web Platform Tests をご存じでしょうか。Web ブラウザ間の相互運用性を高めるための、様々な Web 標準技術に関するテストスイートです。主要 Web ブラウザは Web Platform Tests を開発プロセスに取り込み、互換性の向上やリグレッションの防止を図っています。
Web Platform Tests はあなたが今閲覧に使っているブラウザで実行できます。試しに HTML 標準に関するテストを実行してみましょう。https://wpt.live/ からディレクトリをたどって https://wpt.live/html/syntax/parsing/ に行くと、数多くのテストファイルが並んでいます。
ファイルリストの先頭にある DOMContentLoaded-defer.html
を開いてみてください。あら、テストが失敗してしまいました!
気を取り直して次の Document.getElementsByTagName-foreign-01.html
を開いてみると……今度は成功しています! JavaScript から document.getElementsByTagName
メソッドを呼び出して SVG の要素を取得できることを確かめられました。
ブラウザによっては失敗したテストをバグトラッキングシステムで管理しています。先ほど失敗した DOMContentLoaded-defer というファイル名を Mozilla Bugzilla (Firefox のバグ管理にも使われています) で検索してみると、1242128 - Frequent Win8 TEST-UNEXPECTED-PASS | /html/syntax/parsing/DOMContentLoaded-defer.html | The end: DOMContentLoaded and defer scripts - expected FAIL が出てきました。
議論の場はそこから DOMContentLoaded-defer.html is likely wrong · Issue #4267 · web-platform-tests/wpt に移っています。どうもこれはテストのほうがおかしいのではないかという話が出ていますね。
Web Platfrom Tests は絶対的なものではなく、日々追加・変更されています。開発は GitHub で開かれており、開発への貢献は各 Web ブラウザの品質向上につながります。筆者もかつて縦書きの文書におけるスクロール位置に関するテストを追加したところ、それに関する Chrome の挙動が修正されました。
あなたも Web Platform Tests に参加して Web プラットフォームを進化させていきませんか!
HTML の img 要素と image タグ ― 2021年12月20日 23時40分
この記事は HTML アドベントカレンダーの 20 日目の分、兼 JavaScript アドベントカレンダーの 20 日目の分です。
HTML のタグといえば、要素の開始位置、終了位置、そして属性を指定する記述のことですね。開始タグは小なり記号 (<
) の後に要素名が続きます。しかしながら、タグに既述した名前とは別の名前の要素が生成される場合があります。
以下の JavaScript コードを実行すると、JavaScript コンソールには (IMAGE
ではなく) IMG
と出力されます。タグに記述された名前は image
なのに、img
要素が生成されているのです。
const div = document.createElement('div');
div.innerHTML = '<image src="" alt="">';
console.log(div.firstChild.tagName); // => "IMG"
実はこれは HTML のパース処理における例外的な扱いで、開始タグの名前が image
のとき、内部的にはパースエラーとしつつ名前を img
に読み替えることになっています。Web 製作者の記述ミスを救済しようとする慈悲の心の現れですね。
あくまでも HTML のパース処理における例外なので、JavaScript で document.createElement
メソッドを使って要素を生成するときには適用されません。以下の例では "[object HTMLUnknownElement]"
という文字列が出力されるはずです。(しかし Firefox では "[object HTMLElement]"
という文字列が出力されます。)
const image = document.createElement('image');
console.log(image.toString());
参考文献
ECMAScript 仕様の連鎖生成規則 ― 2021年12月19日 23時58分
この記事は JavaScript アドベントカレンダーの 19 日目の分です。
JavaScript の言語仕様は ECMAScript として策定されています。ECMAScript の仕様書を読んでいくにあたって注意すべき点として、連鎖生成規則 (chain production) に対する挙動の記述が省略されているかもしれないということが挙げられます。筆者は当初このことに気づかず、「この生成規則に対する Runtime Semantics が定義されていないけど、どうなっているんだろう?」と悩みました。
連鎖生成規則とは、finally
節に対する生成規則
- Finally :
- finally Block
のように、生成規則の右辺に非終端記号がひとつだけ存在するものです。終端記号はいくつあっても (ひとつもなくても) 構いません。この場合、Finally 生成規則に対する Runtime Semantics は、その右辺に存在する非終端記号 Block に対する Runtime Semantics がそのまま呼び出されるというものになります。
最近のコメント