کنترل های فرم توانمندتر

Arthur Evans

منتشر شده: ۸ آگوست ۲۰۱۹

بسیاری از توسعه‌دهندگان، کنترل‌های فرم سفارشی می‌سازند، یا برای ارائه کنترل‌هایی که در مرورگر تعبیه نشده‌اند، یا برای سفارشی‌سازی ظاهر و احساس، فراتر از آنچه با کنترل‌های فرم تعبیه‌شده امکان‌پذیر است.

با این حال، تکرار ویژگی‌های کنترل‌های فرم HTML داخلی می‌تواند دشوار باشد. برخی از ویژگی‌هایی را که یک عنصر <input> هنگام اضافه کردن آن به یک فرم به طور خودکار دریافت می‌کند، در نظر بگیرید:

  • ورودی به طور خودکار به لیست کنترل‌های فرم اضافه می‌شود.
  • مقدار ورودی به طور خودکار همراه با فرم ارسال می‌شود.
  • ورودی در اعتبارسنجی فرم شرکت می‌کند. می‌توانید با استفاده از شبه کلاس‌های :valid و :invalid ، ورودی را استایل‌دهی کنید.
  • ورودی هنگام تنظیم مجدد فرم، بارگذاری مجدد فرم یا هنگامی که مرورگر سعی در تکمیل خودکار ورودی‌های فرم دارد، مطلع می‌شود.

کنترل‌های فرم سفارشی معمولاً تعداد کمی از این ویژگی‌ها را دارند. توسعه‌دهندگان می‌توانند برخی از محدودیت‌های جاوا اسکریپت را دور بزنند، مانند اضافه کردن یک <input> مخفی به یک فرم برای شرکت در ارسال فرم. اما سایر ویژگی‌ها را نمی‌توان به تنهایی در جاوا اسکریپت تکرار کرد.

دو ویژگی وب، ساخت کنترل‌های فرم سفارشی را آسان‌تر کرده و محدودیت‌های کنترل‌های سفارشی را از بین می‌برد:

  • رویداد formdata به یک شیء جاوا اسکریپت دلخواه اجازه می‌دهد تا در ارسال فرم شرکت کند، بنابراین می‌توانید داده‌های فرم را بدون استفاده از <input> پنهان اضافه کنید.
  • رابط برنامه‌نویسی کاربردی (API) عناصر سفارشی مرتبط با فرم، به عناصر سفارشی اجازه می‌دهد تا بیشتر شبیه کنترل‌های فرم داخلی عمل کنند.

از این دو ویژگی می‌توان برای ایجاد انواع جدیدی از کنترل‌ها که عملکرد بهتری دارند، استفاده کرد.

API مبتنی بر رویداد

Browser Support

  • کروم: ۵.
  • لبه: ۱۲.
  • فایرفاکس: ۴.
  • سافاری: ۵.

Source

رویداد formdata یک 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> برچسب‌گذاری کرد.
  • این عنصر می‌تواند مقداری را تنظیم کند که به طور خودکار با فرم ارسال می‌شود.
  • این عنصر می‌تواند یک پرچم تنظیم کند که نشان می‌دهد آیا ورودی معتبری دارد یا خیر. اگر یکی از کنترل‌های فرم ورودی نامعتبر داشته باشد، فرم قابل ارسال نیست.
  • این عنصر می‌تواند برای بخش‌های مختلف چرخه حیات فرم، مانند زمانی که فرم غیرفعال می‌شود یا به حالت پیش‌فرض خود بازنشانی می‌شود، فراخوانی‌های برگشتی ارائه دهد.
  • این عنصر از شبه‌کلاس‌های استاندارد CSS برای کنترل‌های فرم، مانند :disabled و :invalid ، پشتیبانی می‌کند.

این سند همه چیز را پوشش نمی‌دهد، اما اصول اولیه مورد نیاز برای ادغام عنصر سفارشی شما با یک فرم را شرح می‌دهد.

تعریف یک عنصر سفارشی مرتبط با فرم

برای تبدیل یک عنصر سفارشی به یک عنصر سفارشی مرتبط با فرم، به چند مرحله اضافی نیاز است:

  • یک ویژگی استاتیک formAssociated به کلاس عنصر سفارشی خود اضافه کنید. این به مرورگر می‌گوید که با عنصر مانند یک کنترل فرم رفتار کند.
  • برای دسترسی به متدها و ویژگی‌های اضافی برای کنترل‌های فرم، مانند setFormValue() و setValidity() متد attachInternals() را روی عنصر فراخوانی کنید.
  • ویژگی‌ها و متدهای رایج پشتیبانی‌شده توسط کنترل‌های فرم، مانند 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 تنظیم شده در markup است. در یک کادر انتخاب، این مربوط به تنظیم ویژگی 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 و عناصر سفارشی مرتبط با فرم در دسترس هستند یا خیر، استفاده کنید. هیچ polyfill برای هیچ یک از این ویژگی‌ها منتشر نشده است. در هر دو مورد، می‌توانید به اضافه کردن یک عنصر فرم پنهان برای انتشار مقدار کنترل به فرم، برگردید.

بسیاری از ویژگی‌های پیشرفته‌تر عناصر سفارشی مرتبط با فرم، احتمالاً به سختی یا غیرممکن می‌توان آن‌ها را چندمنظوره کرد.

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 عناصر سفارشی مرتبط با فرم، می‌توانید مجموعه‌ای جدید از قابلیت‌ها را برای کنترل‌های فرم سفارشی فراهم کنید که مانند کنترل‌های فرم داخلی کار می‌کنند.