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

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

آرثر إيفانز

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

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

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

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