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

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

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 أو إصدار أحدث للاطّلاع على واجهة برمجة التطبيقات أثناء عملها.

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

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

  • Chrome: 5
  • Edge: 12.
  • ‫Firefox: 4
  • Safari: 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() الطريقة على عنصر 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 ، تمامًا مثل عنصر التحكّم في النموذج المضمّن.

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

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

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

مثال عملي

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

رصد الميزات

يمكنك استخدام ميزة رصد الميزات لتحديد ما إذا كان حدث formdata والعناصر المخصّصة المرتبطة بالنموذج متوفّرة. لم يتم إصدار أيّ polyfills حاليًا لأيّ من الميزتَين. في كلتا الحالتَين، يمكنك الرجوع إلى إضافة عنصر نموذج مخفي لنشر قيمة عنصر التحكّم في النموذج. من المحتمل أن يكون من الصعب أو من المستحيل استخدام تقنية الملء اللاحق للعديد من الميزات الأكثر تقدمًا للعناصر المخصّصة المرتبطة بالنماذج.

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> مخفي.

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

الصورة الرئيسية مقدمة من Oudom Pravat على Unsplash.