عناصر تحكّم أكثر فعالية في النموذج

بفضل حدث جديد وواجهات برمجة تطبيقات العناصر المخصصة، أصبحت المشاركة في النماذج أسهل بكثير.

Arthur Evans

ينشئ العديد من المطوّرين عناصر تحكُّم مخصّصة في النماذج، إمّا لتقديم عناصر تحكّم غير مضمَّنة في المتصفّح، أو لتخصيص الشكل والأسلوب بدرجة أكبر مما يمكن تحقيقه باستخدام عناصر التحكّم المضمّنة في النموذج.

ومع ذلك، قد يكون من الصعب تكرار ميزات عناصر التحكم المضمّنة في نماذج HTML. ضع في اعتبارك بعض الميزات التي يحصل عليها عنصر <input> تلقائيًا عند إضافته إلى نموذج:

  • تتم إضافة الإدخال تلقائيًا إلى قائمة عناصر التحكّم في النموذج.
  • يتم إرسال قيمة الإدخال تلقائيًا مع النموذج.
  • ويشارك الإدخال في عملية التحقّق من صحة النموذج. يمكنك تصميم نمط الإدخال باستخدام الفئتَين الزائفتَين :valid و:invalid.
  • يتم إشعار الإدخال عند إعادة ضبط النموذج أو عند إعادة تحميله أو عندما يحاول المتصفّح ملء إدخالات النموذج تلقائيًا.

تحتوي عناصر تحكم النماذج المخصصة عادةً على عدد قليل من هذه الميزات. يستطيع المطوّرون معالجة بعض القيود في JavaScript، مثل إضافة <input> مخفي إلى نموذج للمشاركة في إرسال النموذج. ولكن لا يمكن تكرار ميزات أخرى في JavaScript وحدها.

تعمل ميزتان جديدتان على الويب على تسهيل إنشاء عناصر التحكّم في النماذج المخصّصة وإزالة قيود عناصر التحكّم المخصّصة الحالية:

  • يتيح حدث formdata لكائن JavaScript عشوائي المشاركة في إرسال النموذج، ما يتيح لك إضافة بيانات النموذج بدون استخدام <input> مخفي.
  • تتيح واجهة برمجة التطبيقات للعناصر المخصصة المرتبطة بالنموذج إمكانية العمل كعناصر تحكم مدمجة في النموذج.

يمكن استخدام هاتين الميزتَين لإنشاء أنواع جديدة من عناصر التحكّم التي تعمل بشكل أفضل.

واجهة برمجة التطبيقات القائمة على الأحداث

الحدث formdata هو واجهة برمجة تطبيقات منخفضة المستوى تتيح لأي رمز 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. يُرجى التأكّد من تشغيله على الإصدار 77 من Chrome أو الإصدارات الأحدث للاطّلاع على واجهة برمجة التطبيقات وهي تعمل بشكل صحيح.

توافُق المتصفح

دعم المتصفح

  • 5
  • 12
  • 4
  • 5

المصدر

العناصر المخصّصة المرتبطة بالنموذج

يمكنك استخدام واجهة برمجة التطبيقات القائمة على الأحداث مع أي نوع من المكوّنات، ولكنها تسمح لك فقط بالتفاعل مع عملية الإرسال.

تشارك عناصر التحكّم الموحّدة في النماذج في العديد من مراحل نشاط النموذج إلى جانب عملية الإرسال. تهدف العناصر المخصّصة المرتبطة بالنموذج إلى سد الفجوة بين التطبيقات المصغّرة المخصّصة وعناصر التحكّم المدمَجة. تتطابق العناصر المخصّصة المرتبطة بالنموذج مع العديد من ميزات عناصر النموذج الموحّدة:

  • عند وضع عنصر مخصَّص مرتبط بنموذج في <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 الذي يوفر إمكانية الوصول إلى واجهات برمجة التطبيقات للتحكّم في النماذج. الطريقة الأساسية الأولى منها هي الطريقة 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، تمامًا مثل عنصر تحكّم مضمَّن في النموذج.

عمليات معاودة الاتصال في مراحل النشاط

تتضمّن واجهة برمجة التطبيقات للعنصر المخصّص المرتبط بالنموذج مجموعة من عمليات الاستدعاء الإضافية في مراحل النشاط لربطها بدورة حياة النموذج. تكون عمليات الاستدعاء اختيارية: لا يمكنك تنفيذ استدعاء إلا إذا كان العنصر يحتاج إلى إجراء شيء ما في هذه المرحلة من دورة الحياة.

void formAssociatedCallback(form)

يتم استدعاء عندما يربط المتصفّح العنصر بعنصر نموذج، أو يلغي ربط العنصر بعنصر نموذج.

void formDisabledCallback(disabled)

يتم استدعاء هذه الدالة بعد تغيّر حالة disabled للعنصر، إما بسبب إضافة السمة disabled لهذا العنصر أو إزالتها، أو بسبب تغيُّر حالة disabled في <fieldset> الذي هو أصل هذا العنصر. تمثل المعلمة disabled حالة الإيقاف الجديدة للعنصر. وقد يوقف العنصر، على سبيل المثال، عناصر في shadow 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 مفيدة عندما لا يمكنك استعادة حالة عنصر تحكّم استنادًا إلى القيمة وحدها. على سبيل المثال، لنفترض أنك أنشأت منتقي ألوان بأوضاع متعددة: لوحة ألوان أو عجلة ألوان نموذج أحمر أخضر أزرق. ستكون القيمة القابلة للإرسال هي اللون المحدّد في شكل أساسي، مثل "#7fff00". ولكن لاستعادة عنصر التحكم إلى حالة معيّنة، سيلزمك أيضًا معرفة الوضع الذي كانت فيه، ولذلك قد تبدو state على النحو التالي "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;
}

مثال عملي

يجمع المثال التالي العديد من ميزات العناصر المخصّصة المرتبطة بالنموذج. يُرجى التأكّد من تشغيله على الإصدار 77 من Chrome أو الإصدارات الأحدث للاطّلاع على واجهة برمجة التطبيقات وهي تعمل بشكل صحيح.

رصد الميزات

يمكنك استخدام رصد الميزات لتحديد ما إذا كان حدث 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 والعناصر المخصّصة المرتبطة بالنموذج أدوات جديدة لإنشاء عناصر تحكّم في النماذج المخصّصة.

لا يمنحك حدث formdata أي إمكانات جديدة، ولكنّه يمنحك واجهة لإضافة بيانات النموذج إلى عملية الإرسال، بدون الحاجة إلى إنشاء عنصر <input> مخفي.

توفّر واجهة برمجة تطبيقات العناصر المخصَّصة المرتبطة بالنموذج مجموعة جديدة من الإمكانات لإنشاء عناصر تحكُّم في النماذج المخصَّصة تعمل كعناصر تحكُّم مُدمجة في النماذج.

صورة رئيسية من تصميم "أودوم برافات" على قناة Un التصميم