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

Arthur Evans

تاريخ النشر: 8 آب (أغسطس) 2019

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

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

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

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

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

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

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

واجهة برمجة تطبيقات مستندة إلى الأحداث

Browser Support

  • Chrome: 5.
  • Edge: 12.
  • Firefox: 4.
  • Safari: 5.

Source

الحدث formdata هو واجهة برمجة تطبيقات منخفضة المستوى تتيح لأي رمز 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);
});

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

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

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

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

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

في حالة وجود عنصر تحكم أبسط (على سبيل المثال إدخال رقم)، فمن المحتمل أن تكون القيمة كافية لاستعادة عنصر التحكم إلى حالته السابقة. في حال حذف state عند استدعاء setFormValue()، يتم تمرير القيمة إلى formStateRestoreCallback().

formStateRestoreCallback(state, mode) {
  // Simple case, restore the saved value
  this.value_ = state;
}

رصد الميزات

يمكنك استخدام اكتشاف الميزة لتحديد ما إذا كان حدث 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 واجهة لإضافة بيانات النموذج إلى عملية الإرسال، بدون الحاجة إلى إنشاء عنصر <input> مخفي. باستخدام واجهة برمجة التطبيقات form-associated custom elements، يمكنك توفير مجموعة جديدة من الإمكانات لعناصر التحكّم المخصّصة في النماذج التي تعمل مثل عناصر التحكّم المضمّنة في النماذج.