jQuery والتعامل مع DOM

العمل مع النماذج والتسلسل

15 دقيقة الدرس 28 من 30

التعامل مع النماذج باستخدام jQuery

النماذج أساسية لتطبيقات الويب، وتوفر jQuery طرقاً قوية لجمع بيانات النموذج والتحقق منها وإرسالها بكفاءة. في هذا الدرس، سنتقن تسلسل النماذج وإرسال النماذج عبر AJAX.

الدالة .serialize()

تحول الدالة .serialize() بيانات النموذج إلى سلسلة مشفرة بعنوان URL، مثالية لطلبات GET أو إرسال النماذج:

البنية الأساسية:
var formData = $('#myForm').serialize();
// تُرجع: "name=John&email=john@example.com&age=30"
مثال: إرسال نموذج بسيط
<form id="registration-form">
    <input type="text" name="username" placeholder="اسم المستخدم" required>
    <input type="email" name="email" placeholder="البريد الإلكتروني" required>
    <input type="password" name="password" placeholder="كلمة المرور" required>
    <select name="country">
        <option value="us">الولايات المتحدة</option>
        <option value="uk">المملكة المتحدة</option>
        <option value="ca">كندا</option>
    </select>
    <label>
        <input type="checkbox" name="newsletter" value="yes">
        الاشتراك في النشرة الإخبارية
    </label>
    <button type="submit">تسجيل</button>
</form>
<div id="registration-result"></div>

<script>
$('#registration-form').submit(function(e) {
    e.preventDefault();

    var formData = $(this).serialize();
    console.log('البيانات المسلسلة:', formData);
    // المخرجات: username=john&email=john@example.com&password=secret123&country=us&newsletter=yes

    $.post('/api/register', formData)
        .done(function(response) {
            $('#registration-result').html(
                '<div class="success">تم التسجيل بنجاح!</div>'
            );
        })
        .fail(function(xhr) {
            $('#registration-result').html(
                '<div class="error">فشل التسجيل: ' + xhr.responseJSON.message + '</div>'
            );
        });
});
</script>
ملاحظة: .serialize() تتضمن فقط عناصر النموذج التي لها سمة name وغير معطلة. تُستبعد خانات الاختيار غير المحددة وأزرار الراديو غير المحددة.

الدالة .serializeArray()

تُرجع الدالة .serializeArray() مصفوفة من الكائنات، مما يوفر مرونة أكبر لمعالجة البيانات:

مثال: العمل مع serializeArray()
<form id="profile-form">
    <input type="text" name="firstName" value="John">
    <input type="text" name="lastName" value="Doe">
    <input type="email" name="email" value="john@example.com">
    <input type="number" name="age" value="30">
    <button type="submit">حفظ الملف الشخصي</button>
</form>

<script>
$('#profile-form').submit(function(e) {
    e.preventDefault();

    var formArray = $(this).serializeArray();
    console.log('تنسيق المصفوفة:', formArray);
    /* المخرجات:
    [
        { name: "firstName", value: "John" },
        { name: "lastName", value: "Doe" },
        { name: "email", value: "john@example.com" },
        { name: "age", value: "30" }
    ]
    */

    // تحويل المصفوفة إلى كائن
    var formObject = {};
    $.each(formArray, function(i, field) {
        formObject[field.name] = field.value;
    });
    console.log('تنسيق الكائن:', formObject);
    /* المخرجات:
    {
        firstName: "John",
        lastName: "Doe",
        email: "john@example.com",
        age: "30"
    }
    */

    // إضافة بيانات إضافية
    formObject.timestamp = new Date().toISOString();
    formObject.userId = 123;

    // إرسال كـ JSON
    $.ajax({
        url: '/api/profile',
        method: 'POST',
        contentType: 'application/json',
        data: JSON.stringify(formObject),
        success: function(response) {
            console.log('تم تحديث الملف الشخصي بنجاح');
        }
    });
});
</script>

دالة مساعدة: تحويل serializeArray إلى كائن

دالة تحويل قابلة لإعادة الاستخدام:
// تحويل serializeArray إلى كائن
function serializeObject(form) {
    var obj = {};
    var array = $(form).serializeArray();

    $.each(array, function(i, field) {
        if (obj[field.name]) {
            // معالجة القيم المتعددة (مثل خانات الاختيار بنفس الاسم)
            if (!obj[field.name].push) {
                obj[field.name] = [obj[field.name]];
            }
            obj[field.name].push(field.value);
        } else {
            obj[field.name] = field.value;
        }
    });

    return obj;
}

// الاستخدام
$('#my-form').submit(function(e) {
    e.preventDefault();
    var data = serializeObject(this);
    console.log(data);
});

التعامل مع خانات الاختيار وأزرار الراديو

مثال: خانات اختيار متعددة
<form id="preferences-form">
    <h3>اختر اهتماماتك:</h3>
    <label><input type="checkbox" name="interests[]" value="sports"> الرياضة</label>
    <label><input type="checkbox" name="interests[]" value="music"> الموسيقى</label>
    <label><input type="checkbox" name="interests[]" value="travel"> السفر</label>
    <label><input type="checkbox" name="interests[]" value="technology"> التكنولوجيا</label>

    <h3>اختر جنسك:</h3>
    <label><input type="radio" name="gender" value="male"> ذكر</label>
    <label><input type="radio" name="gender" value="female"> أنثى</label>
    <label><input type="radio" name="gender" value="other"> آخر</label>

    <button type="submit">حفظ التفضيلات</button>
</form>

<script>
$('#preferences-form').submit(function(e) {
    e.preventDefault();

    // استخدام serialize()
    var serialized = $(this).serialize();
    console.log('مسلسل:', serialized);
    // المخرجات: interests[]=sports&interests[]=travel&gender=male

    // الحصول على جميع الاهتمامات المحددة
    var interests = $('input[name="interests[]"]:checked').map(function() {
        return $(this).val();
    }).get();
    console.log('مصفوفة الاهتمامات:', interests);
    // المخرجات: ["sports", "travel"]

    // الحصول على زر الراديو المحدد
    var gender = $('input[name="gender"]:checked').val();
    console.log('الجنس:', gender);
    // المخرجات: "male"

    // بناء كائن مخصص
    var preferences = {
        interests: interests,
        gender: gender,
        userId: 123
    };

    $.ajax({
        url: '/api/preferences',
        method: 'POST',
        contentType: 'application/json',
        data: JSON.stringify(preferences),
        success: function() {
            alert('تم حفظ التفضيلات!');
        }
    });
});
</script>

تحميل الملفات مع FormData

لتحميل الملفات، استخدم FormData API بدلاً من serialize():

مثال: تحميل الملفات مع بيانات النموذج الأخرى
<form id="upload-form" enctype="multipart/form-data">
    <input type="text" name="title" placeholder="عنوان الصورة" required>
    <textarea name="description" placeholder="الوصف"></textarea>
    <input type="file" name="photo" accept="image/*" required>
    <input type="file" name="documents[]" multiple>
    <button type="submit">تحميل</button>
</form>
<div id="upload-progress"></div>
<div id="upload-result"></div>

<script>
$('#upload-form').submit(function(e) {
    e.preventDefault();

    // إنشاء FormData من النموذج
    var formData = new FormData(this);

    // أو بناء FormData يدوياً
    var manualFormData = new FormData();
    manualFormData.append('title', $('#upload-form [name="title"]').val());
    manualFormData.append('description', $('#upload-form [name="description"]').val());
    manualFormData.append('photo', $('#upload-form [name="photo"]')[0].files[0]);

    // إلحاق ملفات متعددة
    var documents = $('#upload-form [name="documents[]"]')[0].files;
    for (var i = 0; i < documents.length; i++) {
        manualFormData.append('documents[]', documents[i]);
    }

    // إضافة بيانات إضافية
    formData.append('userId', 123);
    formData.append('timestamp', new Date().toISOString());

    $.ajax({
        url: '/api/upload',
        method: 'POST',
        data: formData,
        processData: false,  // عدم معالجة البيانات
        contentType: false,  // عدم تعيين نوع المحتوى (multipart/form-data)
        xhr: function() {
            // XMLHttpRequest مخصص لتتبع التقدم
            var xhr = new window.XMLHttpRequest();
            xhr.upload.addEventListener('progress', function(e) {
                if (e.lengthComputable) {
                    var percentComplete = (e.loaded / e.total) * 100;
                    $('#upload-progress').html(
                        '<div class="progress-bar">' +
                        '<div class="progress-fill" style="width: ' + percentComplete + '%"></div>' +
                        '</div>' +
                        '<p>جاري التحميل: ' + percentComplete.toFixed(2) + '%</p>'
                    );
                }
            }, false);
            return xhr;
        },
        success: function(response) {
            $('#upload-result').html('<div class="success">تم التحميل بنجاح!</div>');
            $('#upload-form')[0].reset();
            $('#upload-progress').empty();
        },
        error: function() {
            $('#upload-result').html('<div class="error">فشل التحميل!</div>');
        }
    });
});
</script>
نصيحة: عند استخدام FormData، عيّن دائماً processData: false وcontentType: false في إعدادات AJAX الخاصة بك. تحتاج jQuery إلى إرسال كائن FormData الخام إلى الخادم.

التحقق من النموذج قبل الإرسال

مثال: التحقق من جانب العميل
<form id="order-form">
    <input type="text" id="customer-name" name="name" placeholder="الاسم الكامل" required>
    <input type="email" id="customer-email" name="email" placeholder="البريد الإلكتروني" required>
    <input type="tel" id="customer-phone" name="phone" placeholder="الهاتف" required>
    <input type="number" id="quantity" name="quantity" min="1" max="100" placeholder="الكمية" required>
    <button type="submit">تقديم الطلب</button>
</form>
<div id="validation-errors"></div>

<script>
$('#order-form').submit(function(e) {
    e.preventDefault();

    var errors = [];

    // التحقق من الاسم
    var name = $('#customer-name').val().trim();
    if (name.length < 2) {
        errors.push('يجب أن يكون الاسم حرفين على الأقل');
    }

    // التحقق من البريد الإلكتروني
    var email = $('#customer-email').val().trim();
    var emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    if (!emailRegex.test(email)) {
        errors.push('يرجى إدخال عنوان بريد إلكتروني صالح');
    }

    // التحقق من الهاتف
    var phone = $('#customer-phone').val().trim();
    var phoneRegex = /^\+?[\d\s\-()]+$/;
    if (!phoneRegex.test(phone) || phone.length < 10) {
        errors.push('يرجى إدخال رقم هاتف صالح');
    }

    // التحقق من الكمية
    var quantity = parseInt($('#quantity').val());
    if (isNaN(quantity) || quantity < 1 || quantity > 100) {
        errors.push('يجب أن تكون الكمية بين 1 و100');
    }

    // عرض الأخطاء أو الإرسال
    if (errors.length > 0) {
        var errorHtml = '<div class="error-list"><ul>';
        errors.forEach(function(error) {
            errorHtml += '<li>' + error + '</li>';
        });
        errorHtml += '</ul></div>';
        $('#validation-errors').html(errorHtml);
        return false;
    }

    // مسح الأخطاء
    $('#validation-errors').empty();

    // إرسال النموذج
    var formData = $(this).serialize();
    $.post('/api/orders', formData)
        .done(function(response) {
            alert('تم تقديم الطلب بنجاح! معرف الطلب: ' + response.orderId);
            $('#order-form')[0].reset();
        })
        .fail(function(xhr) {
            $('#validation-errors').html(
                '<div class="error">خطأ في الخادم: ' + xhr.responseJSON.message + '</div>'
            );
        });
});
</script>

التحقق من الحقل في الوقت الفعلي

مثال: التحقق أثناء كتابة المستخدم
<form id="signup-form">
    <div class="form-group">
        <input type="text" id="username" name="username" placeholder="اسم المستخدم">
        <span class="validation-message" id="username-msg"></span>
    </div>
    <div class="form-group">
        <input type="email" id="email" name="email" placeholder="البريد الإلكتروني">
        <span class="validation-message" id="email-msg"></span>
    </div>
    <div class="form-group">
        <input type="password" id="password" name="password" placeholder="كلمة المرور">
        <span class="validation-message" id="password-msg"></span>
    </div>
    <button type="submit">اشتراك</button>
</form>

<script>
// التحقق من توفر اسم المستخدم
var usernameTimeout;
$('#username').on('input', function() {
    var username = $(this).val().trim();
    var $msg = $('#username-msg');

    clearTimeout(usernameTimeout);

    if (username.length < 3) {
        $msg.text('يجب أن يكون اسم المستخدم 3 أحرف على الأقل').removeClass('success').addClass('error');
        return;
    }

    $msg.text('جاري التحقق...').removeClass('error success');

    usernameTimeout = setTimeout(function() {
        $.get('/api/check-username', { username: username })
            .done(function(response) {
                if (response.available) {
                    $msg.text('✓ اسم المستخدم متاح').removeClass('error').addClass('success');
                } else {
                    $msg.text('✗ اسم المستخدم مستخدم بالفعل').removeClass('success').addClass('error');
                }
            })
            .fail(function() {
                $msg.text('تعذر التحقق من التوفر').removeClass('success').addClass('error');
            });
    }, 500); // تأخير: انتظر 500 مللي ثانية بعد توقف المستخدم عن الكتابة
});

// التحقق من تنسيق البريد الإلكتروني
$('#email').on('blur', function() {
    var email = $(this).val().trim();
    var $msg = $('#email-msg');
    var emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;

    if (!email) {
        $msg.text('البريد الإلكتروني مطلوب').removeClass('success').addClass('error');
    } else if (!emailRegex.test(email)) {
        $msg.text('تنسيق بريد إلكتروني غير صالح').removeClass('success').addClass('error');
    } else {
        $msg.text('✓ بريد إلكتروني صالح').removeClass('error').addClass('success');
    }
});

// التحقق من قوة كلمة المرور
$('#password').on('input', function() {
    var password = $(this).val();
    var $msg = $('#password-msg');
    var strength = 0;

    if (password.length >= 8) strength++;
    if (/[a-z]/.test(password) && /[A-Z]/.test(password)) strength++;
    if (/\d/.test(password)) strength++;
    if (/[^a-zA-Z\d]/.test(password)) strength++;

    var messages = [
        'ضعيفة جداً',
        'ضعيفة',
        'متوسطة',
        'قوية',
        'قوية جداً'
    ];

    var colors = ['red', 'orange', 'yellow', 'lightgreen', 'green'];

    $msg.text('قوة كلمة المرور: ' + messages[strength])
        .css('color', colors[strength]);
});
</script>

حقول النموذج الديناميكية

مثال: إضافة/إزالة الحقول ديناميكياً
<form id="team-form">
    <h3>أعضاء الفريق</h3>
    <div id="members-container">
        <div class="member-row">
            <input type="text" name="members[0][name]" placeholder="الاسم" required>
            <input type="email" name="members[0][email]" placeholder="البريد الإلكتروني" required>
            <button type="button" class="remove-member">إزالة</button>
        </div>
    </div>
    <button type="button" id="add-member">إضافة عضو</button>
    <button type="submit">إرسال الفريق</button>
</form>

<script>
var memberIndex = 1;

// إضافة حقل عضو جديد
$('#add-member').click(function() {
    var memberHtml =
        '<div class="member-row">' +
        '<input type="text" name="members[' + memberIndex + '][name]" placeholder="الاسم" required>' +
        '<input type="email" name="members[' + memberIndex + '][email]" placeholder="البريد الإلكتروني" required>' +
        '<button type="button" class="remove-member">إزالة</button>' +
        '</div>';

    $('#members-container').append(memberHtml);
    memberIndex++;
});

// إزالة حقل العضو (باستخدام تفويض الحدث)
$(document).on('click', '.remove-member', function() {
    if ($('.member-row').length > 1) {
        $(this).closest('.member-row').remove();
    } else {
        alert('يجب أن يكون لديك عضو فريق واحد على الأقل');
    }
});

// إرسال النموذج
$('#team-form').submit(function(e) {
    e.preventDefault();

    var formArray = $(this).serializeArray();

    // تجميع الأعضاء
    var members = [];
    var currentMember = {};

    formArray.forEach(function(field) {
        var match = field.name.match(/members\[(\d+)\]\[(\w+)\]/);
        if (match) {
            var index = parseInt(match[1]);
            var property = match[2];

            if (!members[index]) {
                members[index] = {};
            }
            members[index][property] = field.value;
        }
    });

    // إزالة الفتحات الفارغة
    members = members.filter(function(member) {
        return member !== null && member !== undefined;
    });

    console.log('أعضاء الفريق:', members);

    $.ajax({
        url: '/api/teams',
        method: 'POST',
        contentType: 'application/json',
        data: JSON.stringify({ members: members }),
        success: function(response) {
            alert('تم إنشاء الفريق بنجاح!');
            $('#team-form')[0].reset();
        }
    });
});
</script>
تحذير: تحقق دائماً من بيانات النموذج على جانب الخادم، حتى لو كان لديك التحقق من جانب العميل. يمكن تجاوز التحقق من جانب العميل، لذلك فإن التحقق من جانب الخادم ضروري للأمان.

منع الإرسال المزدوج

مثال: تعطيل زر الإرسال أثناء الطلب
<form id="payment-form">
    <input type="text" name="cardNumber" placeholder="رقم البطاقة" required>
    <input type="text" name="cvv" placeholder="CVV" required>
    <button type="submit" id="pay-button">ادفع الآن</button>
</form>

<script>
var isSubmitting = false;

$('#payment-form').submit(function(e) {
    e.preventDefault();

    // منع الإرسال المزدوج
    if (isSubmitting) {
        return false;
    }

    isSubmitting = true;
    var $button = $('#pay-button');
    var originalText = $button.text();

    $button.prop('disabled', true).text('جاري المعالجة...');

    var formData = $(this).serialize();

    $.post('/api/payment', formData)
        .done(function(response) {
            alert('تمت الدفعة بنجاح!');
        })
        .fail(function() {
            alert('فشلت الدفعة. يرجى المحاولة مرة أخرى.');
        })
        .always(function() {
            // إعادة تمكين الزر
            $button.prop('disabled', false).text(originalText);
            isSubmitting = false;
        });
});
</script>
تمرين عملي:

أنشئ نموذج طلب وظيفة كامل بالميزات التالية:

  1. المعلومات الشخصية: الاسم، البريد الإلكتروني، الهاتف
  2. تحميل السيرة الذاتية مع شريط التقدم
  3. خانات اختيار مهارات متعددة (JavaScript، Python، Java، إلخ.)
  4. أزرار راديو مستوى الخبرة (مبتدئ، متوسط، متقدم)
  5. قسم "الوظائف السابقة" الديناميكي (إضافة/إزالة إدخالات الوظائف)
  6. كل إدخال وظيفة: اسم الشركة، المنصب، تاريخ البدء، تاريخ الانتهاء
  7. التحقق من البريد الإلكتروني في الوقت الفعلي
  8. التحقق من صحة تنسيق رقم الهاتف
  9. إرسال النموذج بالكامل عبر AJAX كـ JSON
  10. عرض رسائل النجاح/الخطأ
  11. منع الإرسال المزدوج
  12. مسح النموذج بعد الإرسال الناجح

إضافي: احفظ بيانات النموذج في localStorage كمسودة أثناء ملء المستخدم لها، واستعدها عند إعادة تحميل الصفحة.

الخلاصة

في هذا الدرس، تعلمت:

  • استخدام .serialize() لتحويل النماذج إلى سلاسل مشفرة بعنوان URL
  • استخدام .serializeArray() لمعالجة البيانات بشكل أكثر مرونة
  • تحويل المصفوفات المسلسلة إلى كائنات
  • التعامل مع خانات الاختيار وأزرار الراديو والقيم المتعددة
  • تحميل الملفات باستخدام FormData API
  • تقنيات التحقق من النموذج من جانب العميل
  • التحقق من الحقل في الوقت الفعلي مع التأخير
  • إنشاء حقول النموذج الديناميكية (إضافة/إزالة)
  • منع الإرسال المزدوج للنماذج
  • تتبع تقدم التحميل

لقد أكملت الوحدة 7: AJAX والبيانات! لديك الآن المهارات لبناء تطبيقات ويب ديناميكية تعتمد على البيانات وتتواصل بكفاءة مع الخوادم دون إعادة تحميل الصفحة.