Web Forms 2.0 の解説

反復フォーム制御子モデル

この文書は、 Web Forms 2.0 の2006年9月26日作業原案中の解説の一部を日本語に翻訳したものです。

3.1. 著者への導入

この節は規定の一部ではありません。

たまに、フォームの一部を任意の回数繰り返したいことがあります。 例えば、注文フォームでは項目ごとに1行としたいでしょう。 これまでは、複雑なクライアント側スクリプトを使うか、 新しい行を追加するごとに鯖に要求を送信するかにより実装してきました。

この問題は、 この章で説明する仕組みを使うことで簡単になり、 雛形をマークし、いつどこでこの雛形を繰り返すのかを指定するだけでよくなります。

順を追って例を見ていきましょう。 次に示すのは、3行のフォーム例です。

<!DOCTYPE html>
<html>
 <head>

  <title>注文フォーム例</title>
 </head>
 <body>
  <form>
   <table>
    <tr>
     <th>品名</th>
     <th>数量</th>
    </tr>
    <tr>
     <td><input type="text" name="row0.product" value=""></td>
     <td><input type="text" name="row0.quantity" value="1"></td>
    </tr>
    <tr>
     <td><input type="text" name="row1.product" value=""></td>
     <td><input type="text" name="row1.quantity" value="1"></td>
    </tr>
    <tr>
     <td><input type="text" name="row2.product" value=""></td>
     <td><input type="text" name="row2.quantity" value="1"></td>
    </tr>
   </table>
   <p><button type="submit">提出</button></p>
  </form>
 </body>
</html>

この3行の雛形は、次のようになります。

    <tr>
     <td><input type="text" name="row0.product" value=""></td>
     <td><input type="text" name="row0.quantity" value="1"></td>
    </tr>

ただし、名前はすべて同じになります。 どの行も row0 になるので、 提出時の順序をみないとどの quantity がどの product と対応するのかが不明確になってしまいます。

そこで、 雛形を次のように修正します。

    <tr id="order">
     <td><input type="text" name="row[order].product" value=""></td>
     <td><input type="text" name="row[order].quantity" value="1"></td>
    </tr>

雛形に固有識別子 (order) を与えました。 この識別子は、行索引番号を置換するべき場所を示すために使います。 雛形が複製される時には、四角括弧で囲った雛形の識別子 ([識別子]) を含む属性はすべて、 その識別子 (と括弧) が固有の索引番号で置換されます。

属性がこのように処理されてしまうことを防ぎたければ、 属性値の先頭に NON-BREAKING ZERO-WIDTH SPACE (&#xFEFF;) を入れてください。雛形を複製するときにこの文字は削除され、 属性値の残りはそのまま複製されます。これは、 例えば利用者が変更できる文字列のように、 属性値の内容の残りの部分を著者が決められない時に便利でしょう。

雛形の行を通常の行と区別するために、 雛形は雛形であるという印を加える必要があります。 これは repeat 属性を使って行います。

    <tr id="order" repeat="template">
     <td><input type="text" name="row[order].product" value=""></td>
     <td><input type="text" name="row[order].quantity" value="1"></td>
    </tr>

では、 はじめの表をこのマークで置き換えましょう。

<!DOCTYPE html>
<html>
 <head>
  <title>注文フォーム例</title>
 </head>
 <body>
  <form>
   <table>
    <tr>
     <th>品名</th>
     <th>数量</th>
    </tr>
    <tr id="order" repeat="template">
     <td><input type="text" name="row[order].product" value=""></td>
     <td><input type="text" name="row[order].quantity" value="1"></td>
    </tr>
   </table>
   <p><button type="submit">提出</button></p>
  </form>
 </body>
</html>

雛形の繰り返し回数の既定値は1なので、 1行現れることになります。雛形を何回繰り返すかは、 repeat-start 属性で制御できます。

    ...
    <tr id="order" repeat="template" repeat-start="3">
     <td><input type="text" name="row[order].product" value=""></td>
     <td><input type="text" name="row[order].quantity" value="1"></td>
    </tr>
   </table>

これではじめの例 (3行と空の制御子) と同じになりました。しかし、これはまだ動的ではありません。 利用者が更に行を追加する方法がありません。

これは add ボタンを追加すれば解決します。 add ボタン型は、利用者がそのボタンを押すと、 雛形の複製を追加します。

add ボタンには2種類の使い方があります。1つ目は、 どの雛形を複製するかを明示的に指定する方法です。

   <p><button type="add" template="order">行を追加</button></p>

雛形は button type="add" または input type="add" の要素の template 属性を使って指定します。 template 属性には、ボタンで追加したい雛形の識別子を入れます。

このようなボタンを押すと、 雛形が複製され、複製されてできたブロックが、 その雛形に関連付けられた最後のブロックの直後に挿入されます。 例えば、先の例で3つの行がある状態で、 利用者がこのボタンを押すと、 新しいブロックが3つ目のブロックの直後に挿入されます。

2つ目は、 add ボタンを雛形の中に置き、 その雛形が複製されればボタンも複製されたブロックの中に複製されるという方法です。 この種類のボタンを押すと、雛形は複製され、 そのボタンが含まれるブロックの直後に挿入されます。 例えば、先の例の各行に add ボタンがあり、誰かが最初の行のボタンを押すと、 最初の行と2つ目の行の間に新しい行が挿入されます。

この例では最初の方法だけを使います。

<!DOCTYPE html>
<html>
 <head>
  <title>注文フォーム例</title>
 </head>
 <body>
  <form>
   <table>
    <tr>
     <th>品名</th>
     <th>数量</th>
    </tr>
    <tr id="order" repeat="template" repeat-start="3">
     <td><input type="text" name="row[order].product" value=""></td>
     <td><input type="text" name="row[order].quantity" value="1"></td>
    </tr>
   </table>
   <p><button type="add" template="order">行を追加</button></p>
   <p><button type="submit">提出</button></p>
  </form>
 </body>
</html>

これで利用者は行を追加できるようになりましたが、 削除することはできません。行の削除は remove ボタン型により行います。利用者がこの種類のボタンを押すと、 ボタンが含まれる行が文書から削除されます。

  <button type="remove">この行を削除</button>

これを雛形に追加すれば、 各行に現れるようになります。

    <tr id="order" repeat="template" repeat-start="3">
     <td><input type="text" name="row[order].product" value=""></td>
     <td><input type="text" name="row[order].quantity" value="1"></td>
     <td><button type="remove">この行を削除</button></td>
    </tr>

最終的にこのようになります。

<!DOCTYPE html>
<html>
 <head>
  <title>注文フォーム例</title>
 </head>
 <body>
  <form>
   <table>
    <tr>
     <th>品名</th>
     <th>数量</th>
    </tr>
    <tr id="order" repeat="template" repeat-start="3">
     <td><input type="text" name="row[order].product" value=""></td>
     <td><input type="text" name="row[order].quantity" value="1"></td>
     <td><button type="remove">この行を削除</button></td>
    </tr>
   </table>
   <p><button type="add" template="order">行を追加</button></p>
   <p><button type="submit">提出</button></p>
  </form>
 </body>
</html>

利用者が一度 追加を押し、真ん中の2つの行を削除し、 2つの品名欄に適当に入力し、提出を押すと、 利用者エージェントは次の名前と値の組を提出することになります。

row0.product=some
row0.quantity=1
row3.product=garbage
row3.quantity=1

例の節に更に例を示します。

3.1.1. 更なる機能

反復モデルには他にも機能があります。 例えば、 remove ボタンと同様に雛形内に挿入し、行を上下に移動させることができる、 move-up ボタンと move-down ボタンがあります。

反復雛形を入れ子にすることもできます。 手動で反復フォームを作成する場合と同じように、 名前に階層の概念を盛り込むことを想定しています。例えば、

order0.name
order0.quantity
order0.comment0.text
order0.comment1.text
order1.name
order1.quantity
order1.comment0.text

この方法で、データの構造を失うことなく、長く使われてきた multipart/form-data との互換性を保ち続けることができます。

ここに示した命名方式は適当なものです。 著者は都合が良い任意の命名方式を使うことができます。

3.1.2. 著者への提案

文書中に入力済みの行を挿入する時は、 そのブロックで使いたい反復索引番号を値とする repeat 属性がある行としてください。

例えば、先の表が予め埋められていて、 最初の列は索引番号が 1 で最初の制御子が 太郎像で2つ目が 12、 2つ目の列は索引番号が 2 で値が花子像5 とします。

...
   <table>
    <tr>
     <th>品名</th>
     <th>数量</th>
    </tr>
    <tr repeat="1">
     <td><input type="text" name="row1.product" value="太郎像"></td>
     <td><input type="text" name="row1.quantity" value="12"></td>
     <td><button type="remove">この行を削除</button></td>
    </tr>
    <tr repeat="2">
     <td><input type="text" name="row2.product" value="花子像"></td>
     <td><input type="text" name="row2.quantity" value="5"></td>
     <td><button type="remove">この行を削除</button></td>
    </tr>
    <tr id="order" repeat="template" repeat-start="1">
     <td><input type="text" name="row[order].product" value=""></td>
     <td><input type="text" name="row[order].quantity" value="1"></td>
     <td><button type="remove">この行を削除</button></td>
    </tr>
   </table>
...

入力済みの行は雛形のになければなりません。

入力済みの行は、 どんな内容を含んでいても構いません。雛形と一致する必要はありません。 しかし、反復モデルの一部であるとみなされるためには、 行には repeat 属性があって、数値が与えられていなければなりません。 この値は整数なら何でも構いません。 (例えば、 すべての入力済みの行を -1 とすることもできます。)

HTML 4 に適合する利用者エージェントでこの仕様を実装していないものでは、 雛形は最初の空行として機能し、 addremove のボタンはフォームを提出させることになるので、 鯖は行の挿入や削除をシミュレートできます。

追加や削除のボタンが適合 HTML 4 利用者エージェントで提出ボタンとして働くのは、 button 要素が使われた時だけです。 input ボタンが使われると、 従来の利用者エージェントは代わりに文章入力欄としてレンダリングします。 ですから、著者は button 要素を使うよう推奨します。

3.1.3. 提出された反復ブロックの順序を追跡する

鯖側で使用する枠組が欄の互いの順序を保持しておらず、 同じ名前の値の相対的な順序は保持している場合には、 著者は、隠し制御子をすべての反復ブロックに入れて使うことができます。 隠し制御子の名前はすべてのブロックで同じとしますが、 値は反復索引番号とします。そして、反復ブロックの順序を判断するためには、 この共通の制御子の値の順序を使うことができます。

3.1.4. 反復モデルでできないこと

この仕様は現在の反復ブロックと直接関連付けられていないボタンを使って反復ブロックを上下に移動させる能力については言及しません。

3.1.5. 非 XHTML 文書の小例

この機能は、非 XHTML 文書でも使うことができます。次に示すのは、 SVG を使い、緑の四角形をかちっとすると紫の四角形が現れ、 紫の四角形をかちっとするとそれが消滅するという例です。

<svg xmlns="http://www.w3.org/2000/svg" viewBox(0 0 40 5)
     xmlns:html="http://www.w3.org/1999/xhtml">
 <rect html:repeat="template" id="blob" x="[blob]" y="2" fill="purple" height="0.9" width="0.9"
       onclick="this.removeRepetitionBlock()"/>
 <rect x="0" y="2" fill="green" height="0.9" width="0.9"
       onclick="document.getElementById('blob').addRepetitionBlockByIndex(null, 1)"/>
</svg>

3.7.

この節では、 反復のより実際的な例を示します。

3.7.1. 反復行

次の例は、 表中のフォームに動的に行を追加するために反復雛形をどう使うかを示しています。

<!DOCTYPE html>
<html>
 <head>
  <title>フォーム反復デモ</title>
 </head>
 <body>
  <form action="http://software.hixie.ch/utilities/cgi/test-tools/echo" method="post" enctype="multipart/form-data">
   <table>
    <thead>
     <tr>
      <th>名前</th>
      <th>猫の数</th>
      <th></th>
     </tr>
    </thead>
    <tbody>
     <tr repeat="0">
      <td><input type="text" name="name_0" value="山田太郎"></td>
      <td><input type="text" name="count_0" value="2"></td>
      <td><button type="remove">行を削除</button></td>
     </tr>
     <tr repeat="template" id="row">
      <td><input type="text" name="name_[row]" value=""></td>
      <td><input type="text" name="count_[row]" value="1"></td>
      <td><button type="remove">行を削除</button></td>
     </tr>
    </tbody>
   </table>
   <p>
    <button type="add" template="row">行を追加</button>
    <button type="submit">提出</button>
   </p>
  </form>
 </body>
</html>

はじめ、 2つの行が可視状態となります。それぞれ2つの文章入力制御子があって、 最初の行は値が山田太郎2 で、2つ目の行は値が (空の文章欄) と 1 です。 2つ目の行は、 (暗黙の) repeat-start 属性により、文書が読込まれた時に反復ブロックが1つ追加された結果です。

行を追加ボタンが押されると、 新しい行が追加されます。最初に追加された行は索引番号が 2 となり (既に番号が 01 の反復ブロックがあるからです。)、制御子はそれぞれ name_2count_2 という名前になります。

行を削除ボタンが押されると、その行が削除されます。

3.7.2. 入れ子の反復

前の例は入れ子の反復ブロックや反復ブロックの順序交換や既存の反復ブロックの間への新しい反復ブロックの追加の例を示していませんでしたが、 これらはすべて先述の機能を使って実現できます。

この例は、 入れ子の反復を示しています。

<html xmlns="http://www.w3.org/1999/xhtml">
 <head>
  <title>太陽系</title>
 </head>
 <body>
  <form>
   <h1> 太陽系 </h1>
   <p> <label> 名前: <input name="name"/> </label> </p>
   <h2> 惑星 </h2>
   <ol>
    <li repeat="template" repeat-start="0" id="planets">
     <label> 名前: <input name="planet[planets].name" required="required"/> </label>
     <h3> 衛星 </h3>
     <ul>
      <li repeat="template" repeat-start="0" id="planet[planets].moons">
       <input name="planet[planets].moon[planet[planets].moons]"/>
       <button type="remove">衛星を削除</button>
      </li>
     </ul>
     <p><button type="add" template="planet[planets].moons">衛星を追加</button></p>
     <p><button type="remove">惑星を削除</button></p>
    </li>
   </ol>
   <p><button type="add" template="planets">惑星を追加</button></p>
   <p><button type="submit">提出</button></p>
  </form>
 </body>
</html>

それぞれの入れ子の反復を固有に識別するため、 入れ子の雛形の識別子 (add ボタンがどの雛形のブロックを追加するかを指定するために識別子を使うので必要です。) は先祖の雛形の識別子と索引番号の置換の機能を使って指定していることに注意して下さい。

XML では四角括弧が ID 属性では認められていないので、 先の例は XML DTD で妥当となりません。それでも、 整形式で、この仕様に適合はしています。 XML DTD に照らして妥当とするために、2番目の id 属性の値で、 U+005B OPENING SQUARE BRACKETU+005D CLOSING SQUARE BRACKET の代わりに U+02D1 MODIFIER LETTER HALF TRIANGULAR COLONU+00B7 MIDDLE DOT を使うことができます。