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

Arthur Evans

Опубликовано: 8 августа 2019 г.

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

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

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

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

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

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

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

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

Browser Support

  • Хром: 5.
  • Край: 12.
  • Firefox: 4.
  • Сафари: 5.

Source

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

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

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

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

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

  • Добавьте статическое свойство formAssociated к классу вашего пользовательского элемента. Это сообщит браузеру, что элемент следует обрабатывать как элемент управления формы.
  • Вызовите метод attachInternals() элемента, чтобы получить доступ к дополнительным методам и свойствам элементов управления формы, таким как setFormValue() и setValidity() .
  • Добавьте общие свойства и методы, поддерживаемые элементами управления формы, такие как name , value и 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() может принимать один из трех типов значений:

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

// 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)

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

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