jQuery والتعامل مع DOM
استنساخ العناصر
استنساخ العناصر
استنساخ العناصر هو تقنية قوية لتكرار هياكل DOM بكفاءة. تنشئ طريقة .clone() في jQuery نسخًا عميقة من العناصر، مع الاحتفاظ اختياريًا بمعالجات الأحداث والبيانات.
الاستنساخ الأساسي
تنشئ طريقة .clone() نسخة من العناصر المحددة:
استنساخ بسيط:
// استنساخ عنصر
let clonedDiv = $(".original").clone();
// استنساخ وإلحاق بموقع مختلف
$(".template").clone().appendTo(".container");
// استنساخ عناصر متعددة
let clonedItems = $("li").clone();
// استنساخ وتعديل قبل الإدراج
let newCard = $(".card-template").clone()
.removeClass("template")
.addClass("active");
$(".card-container").append(newCard);
استنساخ بدون معالجات الأحداث:
// استنساخ العنصر فقط (بدون أحداث أو بيانات)
let clone = $(".item").clone();
// هذا هو السلوك الافتراضي
let clone = $(".item").clone(false);
الاستنساخ مع الأحداث والبيانات
يمكنك الحفاظ على معالجات الأحداث والبيانات عند الاستنساخ:
استنساخ مع الأحداث:
// استنساخ مع معالجات الأحداث
let cloneWithEvents = $(".item").clone(true);
// استنساخ مع الأحداث والبيانات
let fullClone = $(".item").clone(true, true);
// مثال: استنساخ زر مع معالج النقر
$("#originalButton").click(function() {
alert("تم النقر على الزر!");
});
// الاستنساخ يحافظ على معالج النقر
let buttonClone = $("#originalButton").clone(true);
$(".button-container").append(buttonClone);
المعاملات:
- المعامل الأول: استنساخ الأحداث والبيانات من العنصر
- المعامل الثاني: استنساخ الأحداث والبيانات من جميع العناصر الفرعية
.clone(withDataAndEvents, deepWithDataAndEvents)- المعامل الأول: استنساخ الأحداث والبيانات من العنصر
- المعامل الثاني: استنساخ الأحداث والبيانات من جميع العناصر الفرعية
مثال عملي: مكرر حقول النموذج
HTML:
<div class="form-repeater">
<div class="form-row">
<input type="text" name="email[]" placeholder="عنوان البريد الإلكتروني">
<button type="button" class="add-row">+ إضافة</button>
<button type="button" class="remove-row" style="display:none">× إزالة</button>
</div>
</div>
jQuery:
$(document).ready(function() {
// إضافة صف جديد
$(document).on("click", ".add-row", function() {
let currentRow = $(this).closest(".form-row");
// استنساخ الصف
let newRow = currentRow.clone(false);
// مسح قيمة الحقل
newRow.find("input").val("");
// إظهار زر الإزالة
newRow.find(".remove-row").show();
// إخفاء زر الإضافة (يظهر فقط في الصف الأخير)
currentRow.find(".add-row").hide();
// إدراج بعد الصف الحالي
currentRow.after(newRow);
});
// إزالة صف
$(document).on("click", ".remove-row", function() {
let currentRow = $(this).closest(".form-row");
let prevRow = currentRow.prev(".form-row");
// إظهار زر الإضافة في الصف السابق
if (prevRow.length) {
prevRow.find(".add-row").show();
}
// إزالة الصف الحالي
currentRow.remove();
});
});
استنساخ القوالب
نمط شائع هو استخدام عناصر قالب مخفية:
نمط قالب HTML:
<div id="card-template" class="card template" style="display: none;">
<h3 class="card-title"></h3>
<p class="card-description"></p>
<button class="card-action">إجراء</button>
</div>
<div id="card-container"></div>
استخدام القوالب:
function createCard(title, description) {
// استنساخ القالب
let card = $("#card-template").clone(true);
// إزالة فئة القالب والمعرف
card.removeAttr("id")
.removeClass("template")
.show();
// تعبئة البيانات
card.find(".card-title").text(title);
card.find(".card-description").text(description);
// إضافة إلى الحاوية
$("#card-container").append(card);
return card;
}
// إنشاء بطاقات متعددة
createCard("بطاقة 1", "وصف البطاقة الأولى");
createCard("بطاقة 2", "وصف البطاقة الثانية");
createCard("بطاقة 3", "وصف البطاقة الثالثة");
تقنيات استنساخ متقدمة
استنساخ مع تعديلات:
// استنساخ مع تعديلات
function duplicateProduct(productId) {
let original = $("#product-" + productId);
let clone = original.clone(false);
// إنشاء معرف جديد
let newId = "product-" + Date.now();
clone.attr("id", newId);
// تحديث المراجع الداخلية
clone.find("[data-product-id]").attr("data-product-id", newId);
// إضافة مؤشر "نسخة"
clone.find(".product-name").append(" (نسخة)");
// إدراج بعد الأصل
original.after(clone);
return clone;
}
// استنساخ صف جدول مع قيم متزايدة
function cloneTableRow(row) {
let clone = $(row).clone(false);
// تحديث رقم الصف
let rowNum = parseInt(clone.find(".row-number").text()) + 1;
clone.find(".row-number").text(rowNum);
// مسح قيم الحقول
clone.find("input").val("");
clone.find("select").prop("selectedIndex", 0);
return clone;
}
مثال عملي: استنساخ عنصر سلة التسوق
HTML:
<div class="product-catalog">
<div class="product" data-id="1" data-price="29.99">
<img src="product1.jpg" alt="منتج 1">
<h4>منتج 1</h4>
<p class="price">99.99 ريال</p>
<button class="add-to-cart">أضف للسلة</button>
</div>
</div>
<div class="cart">
<h3>سلة التسوق</h3>
<div class="cart-items"></div>
<div class="cart-total">الإجمالي: <span>0.00</span> ريال</div>
</div>
jQuery:
$(document).ready(function() {
let cart = [];
// إضافة منتج إلى السلة
$(".add-to-cart").click(function() {
let product = $(this).closest(".product");
let productId = product.data("id");
// التحقق مما إذا كان موجودًا بالفعل في السلة
let existingItem = cart.find(item => item.id === productId);
if (existingItem) {
existingItem.quantity++;
updateCartDisplay();
} else {
// استنساخ المنتج للسلة
let cartItem = product.clone(false);
// تحويل إلى عنصر سلة
cartItem.removeClass("product")
.addClass("cart-item");
// استبدال الزر بعناصر تحكم الكمية
cartItem.find(".add-to-cart").replaceWith(
'<div class="quantity-controls">' +
'<button class="decrease-qty">-</button>' +
'<span class="quantity">1</span>' +
'<button class="increase-qty">+</button>' +
'<button class="remove-item">×</button>' +
'</div>'
);
// إضافة إلى مصفوفة السلة
cart.push({
id: productId,
element: cartItem,
price: product.data("price"),
quantity: 1
});
// عرض في السلة
$(".cart-items").append(cartItem);
updateCartDisplay();
}
});
// زيادة الكمية
$(document).on("click", ".increase-qty", function() {
let cartItem = $(this).closest(".cart-item");
let productId = cartItem.data("id");
let item = cart.find(i => i.id === productId);
if (item) {
item.quantity++;
cartItem.find(".quantity").text(item.quantity);
updateCartDisplay();
}
});
// تقليل الكمية
$(document).on("click", ".decrease-qty", function() {
let cartItem = $(this).closest(".cart-item");
let productId = cartItem.data("id");
let item = cart.find(i => i.id === productId);
if (item && item.quantity > 1) {
item.quantity--;
cartItem.find(".quantity").text(item.quantity);
updateCartDisplay();
}
});
// إزالة عنصر
$(document).on("click", ".remove-item", function() {
let cartItem = $(this).closest(".cart-item");
let productId = cartItem.data("id");
// إزالة من المصفوفة
cart = cart.filter(i => i.id !== productId);
// إزالة من العرض
cartItem.fadeOut(300, function() {
$(this).remove();
updateCartDisplay();
});
});
// تحديث إجمالي السلة
function updateCartDisplay() {
let total = cart.reduce((sum, item) => {
return sum + (item.price * item.quantity);
}, 0);
$(".cart-total span").text(total.toFixed(2));
}
});
أفضل ممارسات الاستنساخ
المشاكل الشائعة والحلول:
// مشكلة: معرفات مكررة
let clone = $("#uniqueElement").clone();
// الحل: إزالة أو تغيير المعرف
clone.attr("id", "uniqueElement-" + Date.now());
// مشكلة: معالجات الأحداث تتضاعف
$(".item").on("click", handler);
let clone = $(".item").clone(true); // المعالج مستنسخ أيضًا
// الحل: استخدام تفويض الأحداث أو الاستنساخ بدون أحداث
// مشكلة: قيم النموذج مستنسخة
let clone = $("form").clone();
// الحل: مسح قيم النموذج بعد الاستنساخ
clone.find("input[type='text'], textarea").val("");
clone.find("input[type='checkbox'], input[type='radio']").prop("checked", false);
clone.find("select").prop("selectedIndex", 0);
// مشكلة: العناصر المخفية تبقى مخفية
let clone = $(".template").clone().show(); // لن يظهر إذا كان الأصل مخفيًا
// الحل: استنساخ أطفال محددين أو إزالة فئة الإخفاء
clone.removeClass("hidden").css("display", "");
تحذير المعرف: قم دائمًا بإزالة أو تعديل خاصية
id عند استنساخ العناصر. تنشئ المعرفات المكررة HTML غير صالح وتتسبب في أخطاء تحديد JavaScript.
اعتبارات الأداء
استنساخ فعال:
// غير فعال: استنساخ داخل حلقة
for (let i = 0; i < 100; i++) {
let clone = $(".template").clone();
clone.find(".index").text(i);
$(".container").append(clone);
}
// أفضل: إلحاق مجمع
let fragments = [];
for (let i = 0; i < 100; i++) {
let clone = $(".template").clone();
clone.find(".index").text(i);
fragments.push(clone);
}
$(".container").append(fragments);
// الأفضل: استخدام DocumentFragment
let fragment = $(document.createDocumentFragment());
for (let i = 0; i < 100; i++) {
let clone = $(".template").clone();
clone.find(".index").text(i);
fragment.append(clone);
}
$(".container").append(fragment);
مثال واقعي: نموذج استطلاع ديناميكي
تطبيق كامل:
$(document).ready(function() {
let questionCounter = 1;
// قالب السؤال
let questionTemplate = `
<div class="question-block">
<div class="question-header">
<span class="question-number"></span>
<button class="remove-question">إزالة</button>
</div>
<input type="text" class="question-text" placeholder="أدخل السؤال">
<div class="options">
<div class="option">
<input type="text" class="option-text" placeholder="خيار 1">
</div>
</div>
<button class="add-option">+ إضافة خيار</button>
</div>
`;
// إضافة السؤال الأول
addQuestion();
// زر إضافة سؤال
$("#addQuestion").click(function() {
addQuestion();
});
// دالة إضافة سؤال
function addQuestion() {
let question = $(questionTemplate);
question.find(".question-number").text("السؤال " + questionCounter);
questionCounter++;
$("#surveyForm").append(question);
}
// إزالة سؤال
$(document).on("click", ".remove-question", function() {
if ($(".question-block").length > 1) {
$(this).closest(".question-block").fadeOut(300, function() {
$(this).remove();
renumberQuestions();
});
} else {
alert("يجب أن يحتوي الاستطلاع على سؤال واحد على الأقل");
}
});
// إضافة خيار إلى السؤال
$(document).on("click", ".add-option", function() {
let optionsContainer = $(this).prev(".options");
let optionCount = optionsContainer.children().length + 1;
let newOption = optionsContainer.children().first().clone(false);
newOption.find(".option-text")
.val("")
.attr("placeholder", "خيار " + optionCount);
optionsContainer.append(newOption);
});
// إعادة ترقيم الأسئلة بعد الإزالة
function renumberQuestions() {
$(".question-block").each(function(index) {
$(this).find(".question-number").text("السؤال " + (index + 1));
});
questionCounter = $(".question-block").length + 1;
}
});
تمرين: أنشئ منشئ وصفات حيث يمكن للمستخدمين:
- استنساخ صفوف المكونات مع أرقام متزايدة تلقائيًا
- استنساخ خطوات التعليمات مع الترقيم الصحيح
- تكرار أقسام الوصفة الكاملة (مع جميع المكونات والخطوات)
- مسح جميع البيانات المستنسخة عند النقر على "وصفة جديدة"
- الحفاظ على التنسيق عند استنساخ حقول النص الغني
إضافي: أضف إعادة ترتيب السحب والإفلات للعناصر المستنسخة ووظيفة الحفظ/التحميل.
الملخص
في هذا الدرس، تعلمت كيفية استنساخ العناصر بفعالية:
- استخدام
.clone()لتكرار عناصر DOM - الاستنساخ مع وبدون الأحداث والبيانات
- العمل مع أنماط القوالب
- تعديل النسخ المستنسخة قبل الإدراج
- التعامل مع المعرفات وقيم النماذج في النسخ المستنسخة
- تحسين الأداء للاستنساخ الجماعي
- بناء نماذج وواجهات ديناميكية
لقد أكملت الآن الوحدة 4: معالجة DOM! لقد تعلمت كيفية إنشاء العناصر وإدراجها واستبدالها وتغليفها وإزالتها واستنساخها. هذه المهارات تشكل أساس واجهات الويب الديناميكية باستخدام jQuery.