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 の iframe 要素の srcdoc 属性で埋め込まれる文書の URL2021年12月15日 23時21分

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


iframe 要素 のsrcdoc 属性の値に HTML 文書のソースコードを記述すると、その HTML 文書を埋め込んだフレームが生成されます。ここで、埋め込まれた HTML 文書の URL はどうなっているでしょうか。

<!-- iframe 要素を含む文書の URL は https://www.example.com/ であるとする。 -->
<iframe srcdoc="
  <a href=&quot;foo?bar=42&amp;amp;baz=23&quot;>Link</a>
  <script>
    console.log(location.href);
    // => "about:srcdoc"

    console.log(document.querySelector('a').href);
    // => "https://www.example.com/foo?bar=42&baz=23"
  </script>
"></iframe>

埋め込まれた文書の URL は about:srcdoc ですが、その文書内のリンクにおいては「iframe 要素を含む文書の URL」を基底 URL として相対 URL が解決されます。

base 要素を使えば文書の URL と異なる URL を基底 URL として使えますが、srcdoc 属性を使って埋め込まれた文書では base 要素がなくてもそのような状態になっているのですね。<iframe src="about:blank"> で埋め込まれた文書も同様の状態になります。

ちなみに、srcdoc 属性で埋め込まれた HTML 文書では文書型宣言 (<!DOCTYPE html>) が省略可能であり、省略した場合も標準準拠モードで描画されます。また、title 要素も省略可能となります。htmlheadbody の各要素の開始タグおよび終了タグはもともと省略可能なため、body 要素の内容が「body 要素内にしか出現しえない要素」から始まるのなら、その要素の開始タグから記述を開始できます。

HTML の属性値とテキストのパース結果の違い2021年12月14日 23時24分

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


HTML の属性値のパース規則は、(一般的な要素の内容として出現する) テキストのパース規則とは少し異なります。

わかりやすいところでは、属性値においては小なり記号 (<) をそのまま書けます。以下の HTML コードにおいて、span 要素の title 属性の値も内容のテキストも結果的には " < " という値になりますが、テキストに関しては内部的にパースエラー (invalid-first-character-of-tag-name; タグの名前の 1 文字目が無効) が発生しています。

<span title=" < "> < </span>

文字参照の扱いにも差異があります。以下のように amp という文字参照の名前の後に ASCII の英数字が続く場合、属性値においては "&ampm" という値に解釈されます。一方、テキストにおいては &amp が不完全な文字参照とみなされ、内部的にパースエラー (missing-semicolon-after-character-reference; 文字参照の後にセミコロンがない) が発生しつつ "&m" という値に解釈されます。

<span title="&ampm">&ampm</span>

この解釈の違いは、文字参照の名前の後に等号 (=) が来る場合も同様です。以下の例で、属性値は "&amp=" という値と解釈され、内容のテキストは内部的にパースエラーが発生しつつ "&=" という値に解釈されます。

<span title="&amp=">&amp=</span>

文字参照の名前の後に ASCII の英数字でも等号でもない文字が来る場合は、属性値でもテキストでも内部的にパースエラーが発生しつつ文字参照として扱われます。以下の例では共に "&*" という値に解釈されます。

<span title="&amp*">&amp*</span>

このような属性値におけるアンパサンドの特殊な扱いにより、クエリパラメータ ampampm を含むような URL https://www.example.com/foo?bar=42&amp=23&ampm=12a 要素の href 属性の値にそのまま (& を文字参照にせずに) 書いたとしても、リンク先 URL は (https://www.example.com/foo?bar&=23&m=12 ではなく) https://www.example.com/foo?bar=42&amp=23&ampm=12 になります。

<a href="https://www.example.com/foo?bar=42&amp=23&ampm=12">...</a>

しかしながら、このような特殊な扱いに依存した記述は意図しない挙動をもたらすことがあるため (属性値の URL をテキスト部分にコピペしたら値が変わってしまうなど)、属性値だろうがテキストだろうが & を記述したいときは文字参照 &amp; を使うことをお勧めします。

HTML の透過的内容モデル2021年12月13日 22時46分

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


その昔、HTML 4 の時代には以下のようなコードは仕様違反でした。a 要素はインライン要素であり、その内容にブロックレベル要素である h3 要素は取れなかったからです。

<a href="url"><h3>見出し</h3></a>

現在の HTML 標準では、a 要素、ins 要素、del 要素などは内容モデルが透過的 (transparent) であるとされ、上述のコードも仕様に適合しています。

透過的内容モデルの要素は、その子要素から見ると透過的です。言い換えると、透過的内容モデルの要素が取れる内容は、その親要素が取れる内容を引き継ぎます。ある a 要素の親要素が div 要素なら、その a 要素の内容はフローコンテンツ (flow content) となります。親要素が p 要素なら、内容は記述コンテンツ (phrasing content) となります。

しかしながら、透過的内容モデルの要素は、その親要素から見ると透過的ではありません。透過的内容モデルの要素はどこにでも出現できるわけではないということです。a 要素、ins 要素、del 要素はいずれもフローコンテンツおよび記述コンテンツのカテゴリに属しており、フローコンテンツまたは記述コンテンツが出現できる個所にしか出現できません。以下のように表の行を囲んだりはできないのです。

<table>
  <tbody>
    <!-- a 要素の位置が不適切 -->
    <a href="url">
      <tr>
        <td>cell</td>
        <td>cell</td>
      </tr>
    </a>
  </tbody>
</table>

a 要素がフローコンテンツを内容に取りうるといっても、a 要素に対するデフォルトスタイルシートは display: inline のままであることに注意が必要です。ブラウザによっては display: inline な要素の子要素に display: block な要素があり、かつ flexbox や grid が複雑に絡み合ったときに、要素の高さの計算がまれに意図しない結果になるようです。子要素に display: block な要素がくるときは、a 要素自身にも display: block を指定しておいたほうが安全かもしれません。

HTML 標準における段落の概念2021年12月12日 21時41分

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


以下の HTML コードを見て、なにか居心地の悪さを感じるでしょうか?

<h1>12日の日記</h1>
天気: 晴れ
<p>今日は動いた。</p>

筆者はどうも居心地の悪さを感じます。「天気: 晴れ」の部分が暗黙的な段落になっているのがその原因です。

HTML 標準において段落 (paragraph) とは、文や記述コンテンツの塊を表す概念です。p 要素は段落を明示しますが、p 要素だけでなく li 要素や div 要素なども段落を表現します。段落の内容が文章である必要はなく、「天気: 晴れ」といった行や住所表記、フォームの入力欄を含む行なども段落となります。

何らかの要素を使って段落を明示しないといけないという決まりはどこにもないのですが、HTML 4 Strict の時代は body 要素直下にブロック要素しかこれなかったこともあり、暗黙的な段落があると p 要素をつけたくなってしまいます。

HTML 4 時代を引きずっているという点では、form 要素の内容も気になります。HTML 4 Strict では form 要素直下にもブロック要素しかこれませんでした。以下のように form 要素直下に記述コンテンツ (phrasing content) が来ていると、

<form aciton="/search">
  <p><label>検索語: <input type="search" name="q"></label></p>
  <button type="submit">検索</button>
  <input type="hidden" name="token" value="deadbeef">
</form>

すべて p 要素で囲みたくなります。

<form aciton="/search">
  <p><label>検索語: <input type="search" name="q"></label></p>
  <p>
    <button type="submit">検索</button>
    <input type="hidden" name="token" value="deadbeef">
  </p>
</form>