より多機能なフォーム コントロール

Arthur Evans

公開日: 2019 年 8 月 8 日

多くの開発者は、ブラウザに組み込まれていないコントロールを提供するため、または組み込みフォーム コントロールで可能な範囲を超えて外観をカスタマイズするために、カスタム フォーム コントロールを構築します。

ただし、組み込みの HTML フォーム コントロールの機能を再現するのは難しい場合があります。いくつかの機能を検討し、<input>要素はフォームに追加すると自動的に取得されます。

  • 入力はフォームのコントロール リストに自動的に追加されます。
  • 入力の値はフォームとともに自動的に送信されます。
  • 入力はフォーム検証に使用されます。入力スタイルは、:validそして:invalid疑似クラス。
  • フォームがリセットされたとき、フォームが再読み込みされたとき、またはブラウザがフォームエントリを自動入力しようとしたときに、入力が通知されます。

カスタム フォーム コントロールには通常、これらの機能はほとんどありません。開発者は JavaScript の制限のいくつかを回避することができます。例えば、隠し要素を追加するなどです。<input>フォームを送信するためにフォームに入力します。しかし、他の機能は JavaScript だけでは再現できません。

次の 2 つの Web 機能により、カスタム フォーム コントロールの構築が容易になり、カスタム コントロールの制限がなくなります。

  • そのformdataイベントは任意の JavaScript オブジェクトをフォーム送信に参加させることができるので、隠しメソッドを使用せずにフォームデータを追加できます。<input>
  • フォームに関連付けられたカスタム要素 API を使用すると、カスタム要素を組み込みのフォーム コントロールのように動作させることができます。

これら 2 つの機能を使用すると、より効果的に機能する新しい種類のコントロールを作成できます。

イベントベースの API

Browser Support

  • Chrome: 5.
  • Edge: 12.
  • Firefox: 4.
  • Safari: 5.

Source

formdata イベントは、任意の JavaScript コードがフォーム送信に参加できるようにする低レベルの API です。

  • 追加formdata対話するフォームにイベント リスナーを追加します。
  • ユーザーが送信をクリックすると、フォームで formdata イベントが発生します。このイベントには、送信されるすべてのデータを保持する FormData オブジェクトが含まれます。
  • formdata リスナーは、フォームが送信される前にデータを追加または変更する機会を得ます。

以下は単一の値を送信する例です。formdataイベントリスナー:

const form = document.querySelector('form');
// FormData event is sent on <form> submission, before transmission.
// The event has a formData property
form.addEventListener('formdata', ({formData}) => {
  // https://developer.mozilla.org/docs/Web/API/FormData
  formData.append('my-input', myInputValue);
});

フォームに関連付けられたカスタム要素

イベント ベースの API はあらゆる種類のコンポーネントで使用できますが、送信プロセスのみと対話できます。

標準化されたフォーム コントロールは、フォーム ライフサイクルの多くの部分に関与します。 フォームに関連付けられたカスタム要素は、カスタム ウィジェットと組み込みコントロール間のギャップを埋めることを目的としています。フォーム関連のカスタム要素は、標準化されたフォーム要素の多くの機能と一致します。

  • フォームに関連付けられたカスタム要素を<form>ブラウザが提供するコントロールのように、フォームに自動的に関連付けられます。
  • 要素には <label> 要素を使用してラベルを付けることができます。
  • 要素は、フォームとともに自動的に送信される値を設定できます。
  • 要素は、有効な入力があるかどうかを示すフラグを設定できます。フォーム コントロールの 1 つに無効な入力がある場合、フォームを送信できません。
  • この要素は、フォームが無効になったときやデフォルトの状態にリセットされたときなど、フォームのライフサイクルのさまざまな部分のコールバックを提供できます。
  • この要素は、:disabled:invalid など、フォーム コントロールの標準の CSS 疑似クラスをサポートしています。

このドキュメントでは、カスタム要素をフォームと統合するために必要な基本事項について説明します。

フォーム関連のカスタム要素を定義する

カスタム要素をフォームに関連付けられたカスタム要素に変換するには、いくつかの追加手順が必要です。

  • 静的を追加するformAssociatedカスタム要素クラスにプロパティを追加します。これは、ブラウザに要素をフォーム コントロールのように扱うように指示します。
  • 要素で attachInternals() メソッドを呼び出して、setFormValue()setValidity() などのフォーム コントロールの追加メソッドとプロパティにアクセスします。
  • フォームコントロールでサポートされている共通のプロパティとメソッドを追加します。namevalue 、 そしてvalidity

これらの項目が基本的なカスタム要素定義にどのように適合するかを次に示します。

// Form-associated custom elements must be autonomous custom elements.
// They must extend HTMLElement, not one of its subclasses.
class MyCounter extends HTMLElement {

  // Identify the element as a form-associated custom element
  static formAssociated = true;

  constructor() {
    super();
    // Get access to the internal form control APIs
    this.internals_ = this.attachInternals();
    // internal value for this control
    this.value_ = 0;
  }

  // Form controls usually expose a "value" property
  get value() { return this.value_; }
  set value(v) { this.value_ = v; }

  // The following properties and methods aren't strictly required,
  // but browser-level form controls provide them. Providing them helps
  // ensure consistency with browser-provided controls.
  get form() { return this.internals_.form; }
  get name() { return this.getAttribute('name'); }
  get type() { return this.localName; }
  get validity() {return this.internals_.validity; }
  get validationMessage() {return this.internals_.validationMessage; }
  get willValidate() {return this.internals_.willValidate; }

  checkValidity() { return this.internals_.checkValidity(); }
  reportValidity() {return this.internals_.reportValidity(); }

  
}
customElements.define('my-counter', MyCounter);

登録すると、ブラウザが提供するフォーム コントロールを使用する場所であればどこでもこの要素を使用できるようになります。

<form>
  <label>Number of bunnies: <my-counter></my-counter></label>
  <button type="submit">Submit</button>
</form>

値を設定する

そのattachInternals()メソッドはElementInternalsフォーム コントロール API へのアクセスを提供するオブジェクト。最も基本的なのは setFormValue() メソッドです。このメソッドは、コントロールの現在の値を設定します。

setFormValue() メソッドは、次の 3 種類の値のいずれかを受け取ることができます。

  • 文字列値
  • File オブジェクト。
  • FormData オブジェクト。 FormData オブジェクトを使用して複数の値を渡すことができます。たとえば、クレジットカード入力コントロールでは、カード番号、有効期限、確認コードを渡す場合があります。

値を設定するには:

this.internals_.setFormValue(this.value_);

複数の値を設定するには、次のようにします。

// Use the control's name as the base name for submitted data
const n = this.getAttribute('name');
const entries = new FormData();
entries.append(n + '-first-name', this.firstName_);
entries.append(n + '-last-name', this.lastName_);
this.internals_.setFormValue(entries);

入力検証

コントロールは、internals オブジェクトの setValidity() メソッドを呼び出すことで、フォームの検証に参加することもできます。

// Assume this is called whenever the internal value is updated
onUpdateValue() {
  if (!this.matches(':disabled') && this.hasAttribute('required') &&
      this.value_ < 0) {
    this.internals_.setValidity({customError: true}, 'Value cannot be negative.');
  }
  else {
    this.internals_.setValidity({});
  }
  this.internals.setFormValue(this.value_);
}

組み込みのフォーム コントロールと同様に、:valid 疑似クラスと :invalid 疑似クラスを使用して、フォームに関連付けられたカスタム要素のスタイルを設定できます。

ライフサイクル コールバック

フォームに関連付けられたカスタム要素 API には、フォームのライフサイクルに結び付ける追加のライフサイクル コールバックのセットが含まれています。コールバックは省略可能です。要素がライフサイクルのその時点で何かを行う必要がある場合にのみ、コールバックを実装します。

void formAssociatedCallback(form)

ブラウザが要素をフォーム要素に関連付けるとき、または要素をフォーム要素から関連付け解除するときに呼び出されます。

void formDisabledCallback(disabled)

後に呼び出されるdisabled要素の変化は、disabledこの要素の属性が追加または削除されたか、disabled状態が変更されました<fieldset>それはこの要素の祖先です。

たとえば、要素が無効になっている場合、そのシャドウ DOM 内の要素を無効にできます。

void formResetCallback()

フォームがリセットされた後に呼び出されます。要素は、何らかのデフォルト状態にリセットされる必要があります。のために<input>要素を設定するには、通常、value一致するプロパティvalueマークアップで設定される属性。チェックボックスの場合、これは設定に関係しますchecked一致するプロパティchecked属性。

void formStateRestoreCallback(state, mode)

次の 2 つの状況のいずれかで呼び出されます。

  • ナビゲーション後やブラウザの再起動時など、ブラウザが要素の状態を復元するとき。mode 引数は "restore" です。
  • フォームの自動入力などのブラウザの入力支援機能によって値が設定される場合。mode 引数は "autocomplete" です。

最初の引数の型は、setFormValue() メソッドの呼び出し方法によって異なります。

フォームの状態を復元する

ページに戻る場合やブラウザを再起動する場合など、状況によっては、ブラウザがフォームをユーザーが最後に残した状態に戻そうとすることがあります。

フォームに関連付けられたカスタム要素の場合、復元された状態は、setFormValue()方法。前の例で示したように、単一の値パラメータを指定してメソッドを呼び出すことも、2 つのパラメータを指定して呼び出すこともできます。

this.internals_.setFormValue(value, state);

value は、コントロールの送信可能な値を表します。オプションstateパラメータは内部コントロールの状態の表現。サーバーに送信されないデータが含まれる場合もあります。state パラメータは、value パラメータと同じ型(文字列、File オブジェクト、FormData オブジェクト)を受け取ります。

そのstateパラメータは、値のみに基づいてコントロールの状態を復元できない場合に便利です。たとえば、パレットや RGB カラーホイールなど複数のモードを持つカラーピッカーを作成するとします。送信可能なは、"#7fff00" などの標準形式で選択された色です。コントロールを特定の状態に復元するには、どのモードだったかも知る必要があるため、状態"palette/#7fff00" のようになります。

this.internals_.setFormValue(this.value_,
    this.mode_ + '/' + this.value_);

コードは、保存された状態の値に基づいて状態を復元する必要があります。

formStateRestoreCallback(state, mode) {
  if (mode == 'restore') {
    // expects a state parameter in the form 'controlMode/value'
    [controlMode, value] = state.split('/');
    this.mode_ = controlMode;
    this.value_ = value;
  }
  // Chrome doesn't handle autofill for form-associated custom elements.
  // In the autofill case, you might need to handle a raw value.
}

よりシンプルなコントロール(数値入力など)の場合、値はコントロールを以前の状態に戻すのに十分である可能性があります。setFormValue() の呼び出し時に state を省略すると、値は formStateRestoreCallback() に渡されます。

formStateRestoreCallback(state, mode) {
  // Simple case, restore the saved value
  this.value_ = state;
}

特徴検出

機能検出を使用して、formdata イベントとフォーム関連のカスタム要素が利用可能かどうかを判断できます。どちらの機能についても、リリースされたポリフィルはありません。どちらの場合も、非表示のフォーム要素を追加して、コントロールの値をフォームに伝播させる方法にフォールバックできます。

フォーム関連のカスタム要素の高度な機能の多くは、ポリフィルが難しいか、不可能である可能性があります。

if ('FormDataEvent' in window) {
  // formdata event is supported
}

if ('ElementInternals' in window &&
    'setFormValue' in window.ElementInternals.prototype) {
  // Form-associated custom elements are supported
}

formdata イベントを使用すると、非表示の <input> 要素を作成することなく、フォームデータを送信プロセスに追加するインターフェースが提供されます。フォーム関連のカスタム要素 API を使用すると、組み込みのフォーム コントロールのように動作するカスタム フォーム コントロールの新しい機能セットを提供できます。