Более функциональные элементы управления формой

Благодаря новому событию и API пользовательских элементов участие в формах стало намного проще.

Arthur Evans

Многие разработчики создают собственные элементы управления формой либо для предоставления элементов управления, которые не встроены в браузер, либо для настройки внешнего вида, превосходящего возможности встроенных элементов управления формой.

Однако может быть сложно воспроизвести функции встроенных элементов управления форм HTML. Рассмотрим некоторые возможности, которые элемент <input> получает автоматически при добавлении его в форму:

  • Ввод автоматически добавляется в список элементов управления формы.
  • Значение ввода автоматически отправляется вместе с формой.
  • Ввод участвует в проверке формы . Вы можете стилизовать ввод, используя псевдоклассы :valid и :invalid .
  • Ввод уведомляется, когда форма сбрасывается, когда форма перезагружается или когда браузер пытается автоматически заполнить записи формы.

Настраиваемые элементы управления формой обычно имеют некоторые из этих функций. Разработчики могут обойти некоторые ограничения JavaScript, например добавить в форму скрытый <input> для участия в отправке формы. Но другие функции невозможно воспроизвести только на JavaScript.

Две новые веб-функции упрощают создание пользовательских элементов управления форм и снимают ограничения существующих пользовательских элементов управления:

  • Событие formdata позволяет произвольному объекту JavaScript участвовать в отправке формы, поэтому вы можете добавлять данные формы без использования скрытого <input> .
  • API пользовательских элементов, связанных с формой, позволяет пользовательским элементам действовать как встроенные элементы управления формой.

Эти две функции можно использовать для создания новых видов элементов управления, которые работают лучше.

API на основе событий

Событие formdata — это низкоуровневый API, который позволяет любому коду JavaScript участвовать в отправке формы. Механизм работает следующим образом:

  1. Вы добавляете прослушиватель событий formdata в форму, с которой хотите взаимодействовать.
  2. Когда пользователь нажимает кнопку отправки, форма запускает событие formdata , которое включает в себя объект FormData , содержащий все отправляемые данные.
  3. Каждый прослушиватель 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);
});

Попробуйте это, используя наш пример с Glitch. Обязательно запустите его в Chrome 77 или более поздней версии, чтобы увидеть API в действии.

Совместимость с браузером

Поддержка браузера

  • Хром: 5.
  • Край: 12.
  • Фаерфокс: 4.
  • Сафари: 5.

Источник

Пользовательские элементы, связанные с формой

Вы можете использовать API на основе событий с любым компонентом, но он позволяет вам взаимодействовать только с процессом отправки.

Стандартизированные элементы управления формой участвуют во многих этапах жизненного цикла формы, помимо отправки. Пользовательские элементы, связанные с формой, призваны устранить разрыв между пользовательскими виджетами и встроенными элементами управления. Пользовательские элементы, связанные с формой, соответствуют многим функциям стандартизированных элементов формы:

  • Когда вы помещаете пользовательский элемент, связанный с формой, внутри <form> , он автоматически связывается с формой, как элемент управления, предоставляемый браузером.
  • Элемент можно пометить с помощью элемента <label> .
  • Элемент может установить значение, которое автоматически отправляется вместе с формой.
  • Элемент может установить флаг, указывающий, имеет ли он действительный ввод. Если в одном из элементов управления формы введен недопустимый ввод, форма не может быть отправлена.
  • Элемент может предоставлять обратные вызовы для различных частей жизненного цикла формы, например, когда форма отключена или сброшена в состояние по умолчанию.
  • Этот элемент поддерживает стандартные псевдоклассы CSS для элементов управления формой, такие как :disabled и :invalid .

Это много функций! В этой статье не будут затронуты все из них, но будут описаны основы, необходимые для интеграции вашего пользовательского элемента с формой.

Определение пользовательского элемента, связанного с формой

Чтобы превратить пользовательский элемент в пользовательский элемент, связанный с формой, необходимо выполнить несколько дополнительных шагов:

  • Добавьте статическое свойство formAssociated в свой класс пользовательского элемента. Это указывает браузеру относиться к элементу как к элементу управления формой.
  • Вызовите метод attachInternals() элемента, чтобы получить доступ к дополнительным методам и свойствам элементов управления формы, таким как setFormValue() и setValidity() .
  • Добавьте общие свойства и методы, поддерживаемые элементами управления формой, такие как name , value и validity .

Вот как эти элементы вписываются в базовое определение пользовательского элемента:

// Form-associated custom elements must be autonomous custom elements--
// meaning 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() может принимать значения одного из трех типов:

  • Строковое значение.
  • Объект 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);

Проверка ввода

Ваш элемент управления также может участвовать в проверке формы, вызывая метод 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> , который является предком этого элемента. Параметр disabled представляет новое отключенное состояние элемента. Например, элемент может отключать элементы в своем теневом DOM, когда он отключен.

void formResetCallback()

Вызывается после сброса формы. Элемент должен вернуться в какое-то состояние по умолчанию. Для элементов <input> это обычно включает установку свойства value в соответствии с атрибутом value , установленным в разметке (или, в случае флажка, установку свойства checked в соответствии с атрибутом checked ).

void formStateRestoreCallback(state, mode)

Вызывается в одном из двух случаев:

  • Когда браузер восстанавливает состояние элемента (например, после навигации или при перезапуске браузера). Аргументом mode в данном случае является "restore" .
  • Когда функции помощи при вводе браузера, такие как автозаполнение форм, устанавливают значение. Аргументом mode в данном случае является "autocomplete" .

Тип первого аргумента зависит от того, как был вызван метод setFormValue() . Подробнее см. Восстановление состояния формы .

Восстановление состояния формы

В некоторых случаях, например при переходе обратно на страницу или перезапуске браузера, браузер может попытаться восстановить форму в то состояние, в котором ее оставил пользователь.

Для пользовательского элемента, связанного с формой, восстановленное состояние получается из значений, которые вы передаете методу setFormValue() . Вы можете вызвать метод с одним параметром-значением, как показано в предыдущих примерах , или с двумя параметрами:

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 currently doesn't handle autofill for form-associated
  // custom elements. In the autofill case, you might need to handle
  // a raw value.
}

В случае более простого элемента управления (например, ввода числа) этого значения, вероятно, будет достаточно, чтобы вернуть элемент управления в предыдущее состояние. Если вы опустите state при вызове setFormValue() , тогда значение будет передано в formStateRestoreCallback() .

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

Рабочий пример

В следующем примере объединены многие функции пользовательских элементов, связанных с формой. Обязательно запустите его в Chrome 77 или более поздней версии, чтобы увидеть API в действии.

Обнаружение функций

Вы можете использовать обнаружение функций, чтобы определить, доступны ли событие 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 и настраиваемые элементы, связанные с формой, предоставляют новые инструменты для создания настраиваемых элементов управления формой.

Событие formdata не дает вам никаких новых возможностей, но дает интерфейс для добавления данных формы в процесс отправки без необходимости создания скрытого элемента <input> .

API пользовательских элементов, связанных с формой, предоставляет новый набор возможностей для создания пользовательских элементов управления формой, которые работают как встроенные элементы управления формой.

Изображение героя, созданное Удомом Праватом на Unsplash .