الخطوات
-
1
ضع النموذج بقيود HTML5
استخدم سمات HTML5 الأصلية للتحقق الأساسي قبل تشغيل JavaScript.
requiredوtype="email"وminlengthوpatternتمنع الإرسالات الواضحة الخطأ وهي متاحة افتراضياً. أضف عنصراً مجاوراً لكل حقل لعرض رسائل خطأ مخصصة.html<form id="register-form" novalidate> <div class="field"> <label for="name">Name</label> <input id="name" name="name" type="text" required minlength="2"> <span class="field-error" aria-live="polite"></span> </div> <div class="field"> <label for="email">Email</label> <input id="email" name="email" type="email" required> <span class="field-error" aria-live="polite"></span> </div> <button type="submit">Register</button> </form> -
2
اعترض الإرسال بـ preventDefault
أضف مستمع
submitعلى عنصر<form>— لا على الزر. استدعِevent.preventDefault()فوراً لإيقاف تنقل المتصفح الافتراضي. هذا يمنحك تحكماً كاملاً فيما يحدث بعد ذلك.javascriptconst form = document.getElementById('register-form'); form.addEventListener('submit', async (event) => { event.preventDefault(); await handleSubmit(form); }); -
3
تحقق من الحقول قبل الإرسال
استخدم Constraint Validation API (
input.validity) للتحقق من كل حقل. اكتب دالة مساعدة صغيرة تقرأ حالة التحقق وتُعيد رسالة مقروءة. امسح الأخطاء السابقة أولاً، ثم أعِد التحقق.javascriptfunction getValidationMessage(input) { if (input.validity.valueMissing) return 'This field is required.'; if (input.validity.typeMismatch) return `Please enter a valid ${input.type}.`; if (input.validity.tooShort) return `Minimum ${input.minLength} characters required.`; if (input.validity.patternMismatch) return input.title || 'Invalid format.'; return ''; } function validateForm(form) { let valid = true; form.querySelectorAll('input').forEach((input) => { const msg = getValidationMessage(input); showFieldError(input, msg); if (msg) valid = false; }); return valid; } -
4
أظهر الأخطاء وامسحها لكل حقل
لكل input عنصر
.field-errorمجاور. اكتب دالتين مساعدتين — واحدة لضبط الخطأ وأخرى لمسحه. أضف كلاسis-invalidللـ input لتتمكن من تنسيق الحدود الحمراء عبر CSS.javascriptfunction showFieldError(input, message) { const errorEl = input.closest('.field')?.querySelector('.field-error'); if (!errorEl) return; if (message) { errorEl.textContent = message; input.classList.add('is-invalid'); input.setAttribute('aria-invalid', 'true'); } else { errorEl.textContent = ''; input.classList.remove('is-invalid'); input.removeAttribute('aria-invalid'); } } // Clear errors when the user starts fixing them form.querySelectorAll('input').forEach((input) => { input.addEventListener('input', () => showFieldError(input, '')); }); -
5
اجمع البيانات وعطّل زر الإرسال
new FormData(form)تقرأ كل<input>و<select>و<textarea>مسماة تلقائياً. عطّل زر الإرسال قبل الطلب لمنع الإرسال المزدوج، وأعِد تفعيله في كتلةfinally.javascriptasync function handleSubmit(form) { if (!validateForm(form)) return; const btn = form.querySelector('[type="submit"]'); btn.disabled = true; btn.textContent = 'Sending…'; const formData = new FormData(form); try { const response = await fetch('/api/register', { method: 'POST', body: formData, // No Content-Type header: browser sets multipart boundary }); if (response.status === 422) { const { errors } = await response.json(); applyServerErrors(form, errors); return; } if (!response.ok) throw new Error(`Server error: ${response.status}`); form.reset(); showSuccess('Registration complete!'); } catch (err) { showError(err.message); } finally { btn.disabled = false; btn.textContent = 'Register'; } } -
6
وزّع أخطاء التحقق 422 على الحقول
تُعيد ردود السيرفر لأخطاء التحقق (HTTP 422) عادةً كائن JSON يُعيّن أسماء الحقول على مصفوفات أخطاء. كرّر على كائن الأخطاء واستدعِ
showFieldErrorلكل حقل. هذا يُغلق حلقة التغذية الراجعة بنظافة بدون إعادة تحميل الصفحة.javascript// Server returns: { errors: { email: ['Already taken.'], name: ['Too short.'] } } function applyServerErrors(form, errors) { Object.entries(errors).forEach(([fieldName, messages]) => { const input = form.querySelector(`[name="${fieldName}"]`); if (input) { showFieldError(input, messages[0]); // Show first error per field } }); } -
7
أرسل JSON بدلاً من FormData عند الحاجة
إذا كان الـ API يتوقع جسم JSON (مثل REST API لا يقبل multipart)، حوّل
FormDataإلى كائن بسيط وحوّله إلى نص. اضبط رأسContent-Typeصراحةً.javascriptconst payload = Object.fromEntries(new FormData(form)); const response = await fetch('/api/register', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), });
نصائح ومحاذير
- أضف <code>novalidate</code> لعنصر النموذج لإلغاء فقاعات أخطاء المتصفح الأصلية والتحكم الكامل في تجربة المستخدم عبر JavaScript.
- أعِد تفعيل زر الإرسال دائماً في <code>finally</code>، لا في فرع النجاح فقط — وإلا ستتركه الأخطاء معطلاً بشكل دائم.
- أخبر قارئات الشاشة بأخطاء التحقق عبر <code>aria-live="polite"</code> على عناصر الأخطاء، و<code>aria-invalid="true"</code> على الحقل المخطئ.
- لمربعات الاختيار والأزرار الدائرية، لا تُضمّن <code>FormData</code> إلا القيم المُحددة. إذا احتاج المربع غير المحدد إلى إرسال <code>false</code>، حوّل إلى كائن بسيط وتعامل معه صراحةً.
- لا تثق أبداً بالتحقق من جانب العميل وحده — تحقق دائماً من جهة السيرفر. التحقق من العميل هو تجربة مستخدم، ليس أمناً.
خاتمة
معالج النماذج المتين ليس كثير الكود — نحو 60 سطراً تُغطي التحقق والإرسال وعرض الأخطاء والرحلة مع السيرفر. النمط هنا قابل لإعادة الاستخدام: بدّل الـ endpoint وأي مجموعة حقول. أتقنه مرة واحدة ولن تحتاج مكتبة نماذج في أغلب مشاريعك.