jQuery والتعامل مع DOM
كائن الحدث والمنع
إتقان كائن الحدث
يحتوي كائن الحدث على معلومات قيمة حول الحدث الذي وقع ويوفر طرقاً للتحكم في سلوكه. فهم كائن الحدث ضروري للتعامل المتقدم مع الأحداث.
ما هو كائن الحدث؟
عندما يحدث حدث، تنشئ jQuery كائن حدث بخصائص وطرق. يتم تمرير هذا الكائن تلقائياً كمعامل أول لدالة معالج الحدث الخاصة بك.
الوصول إلى كائن الحدث:
$("button").on("click", function(event) {
console.log(event); // كائن الحدث بأكمله
console.log("النوع:", event.type); // "click"
console.log("الهدف:", event.target); // العنصر الذي أطلق
console.log("الطابع الزمني:", event.timeStamp); // متى حدث
});
// استخدام دالة سهمية (ملاحظة: 'this' لن يعمل)
$("button").on("click", (e) => {
console.log("نوع الحدث:", e.type);
});
خصائص كائن الحدث الأساسية
الخصائص الشائعة:
$("a").on("click", function(event) {
// معلومات الحدث
console.log("النوع:", event.type); // "click"
console.log("الطابع الزمني:", event.timeStamp); // الوقت بالملي ثانية
// عناصر الهدف
console.log("الهدف:", event.target); // العنصر المنقور
console.log("الهدف الحالي:", event.currentTarget); // العنصر الذي يعالج الحدث
console.log("الهدف المرتبط:", event.relatedTarget); // عنصر مرتبط (مثل التمرير)
// موضع الماوس
console.log("pageX:", event.pageX); // X بالنسبة للمستند
console.log("pageY:", event.pageY); // Y بالنسبة للمستند
console.log("clientX:", event.clientX); // X بالنسبة لمنفذ العرض
console.log("clientY:", event.clientY); // Y بالنسبة لمنفذ العرض
console.log("offsetX:", event.offsetX); // X بالنسبة للعنصر
console.log("offsetY:", event.offsetY); // Y بالنسبة للعنصر
// زر الماوس والمعدلات
console.log("which:", event.which); // 1=يسار، 2=وسط، 3=يمين
console.log("ctrlKey:", event.ctrlKey); // true إذا كان Ctrl مضغوطاً
console.log("shiftKey:", event.shiftKey); // true إذا كان Shift مضغوطاً
console.log("altKey:", event.altKey); // true إذا كان Alt مضغوطاً
console.log("metaKey:", event.metaKey); // true إذا كان Cmd/Win مضغوطاً
// لوحة المفاتيح (لـ keydown/keyup)
console.log("المفتاح:", event.key); // اسم المفتاح (مثل "Enter")
console.log("كود المفتاح:", event.keyCode); // الكود الرقمي (مهمل)
});
event.target مقابل event.currentTarget
فهم الفرق:
<div id="parent" style="padding: 50px; background: lightblue;">
الأصل
<button id="child">زر الطفل</button>
</div>
<script>
$("#parent").on("click", function(event) {
console.log("الهدف:", event.target.id); // "child" (العنصر المنقور)
console.log("الهدف الحالي:", event.currentTarget.id); // "parent" (العنصر الذي يعالج)
// استخدام jQuery
console.log("الهدف هو زر:", $(event.target).is("button"));
console.log("هذا هو الأصل:", $(this).attr("id")); // "parent"
});
</script>
تذكر:
event.target هو العنصر الذي أطلق الحدث، بينما event.currentTarget هو العنصر المرفق به المعالج (نفس this).
منع السلوك الافتراضي
event.preventDefault() يوقف الإجراء الافتراضي للمتصفح للحدث:
حالات الاستخدام الشائعة:
// منع إرسال النموذج
$("form").on("submit", function(event) {
event.preventDefault();
console.log("لم يتم إرسال النموذج - سنتعامل معه باستخدام AJAX");
});
// منع التنقل بالرابط
$("a.no-follow").on("click", function(event) {
event.preventDefault();
alert("تم منع التنقل بالرابط");
});
// منع قائمة السياق
$(document).on("contextmenu", function(event) {
event.preventDefault();
alert("النقر الأيمن معطل");
});
// منع تحديد النص
$(".no-select").on("selectstart", function(event) {
event.preventDefault();
});
// منع سلوك السحب
$("img").on("dragstart", function(event) {
event.preventDefault();
});
إيقاف انتشار الأحداث
event.stopPropagation() يمنع الحدث من التصاعد عبر شجرة DOM:
مثال stopPropagation():
<div id="outer" style="padding: 50px; background: lightblue;">
خارجي
<div id="inner" style="padding: 30px; background: lightcoral;">
داخلي
<button id="button">زر</button>
</div>
</div>
<script>
$("#outer").on("click", function() {
console.log("تم النقر على الخارجي");
});
$("#inner").on("click", function(event) {
console.log("تم النقر على الداخلي");
event.stopPropagation(); // يمنع ظهور "تم النقر على الخارجي"
});
$("#button").on("click", function(event) {
console.log("تم النقر على الزر");
// بدون stopPropagation، ستُطلق جميع المعالجات الثلاثة
event.stopPropagation();
});
</script>
stopImmediatePropagation()
يوقف الانتشار ويمنع معالجات أخرى على نفس العنصر من التنفيذ:
stopImmediatePropagation() مقابل stopPropagation():
$("button").on("click", function(event) {
console.log("المعالج 1");
event.stopImmediatePropagation(); // يوقف كل شيء
});
$("button").on("click", function(event) {
console.log("المعالج 2"); // هذا لن يعمل!
});
// قارن مع stopPropagation():
$("button").on("click", function(event) {
console.log("المعالج 1");
event.stopPropagation(); // يوقف التصاعد فقط
});
$("button").on("click", function(event) {
console.log("المعالج 2"); // هذا سيعمل
});
التحقق من منع الافتراضي
isDefaultPrevented():
$("a").on("click", function(event) {
event.preventDefault();
if (event.isDefaultPrevented()) {
console.log("تم منع الإجراء الافتراضي");
}
});
مثال عملي: التحقق من النموذج
التحقق المتقدم من النموذج:
<form id="signupForm">
<input type="text" id="username" placeholder="اسم المستخدم" required>
<input type="email" id="email" placeholder="البريد الإلكتروني" required>
<input type="password" id="password" placeholder="كلمة المرور" required>
<button type="submit">التسجيل</button>
</form>
<div id="errors"></div>
<script>
$("#signupForm").on("submit", function(event) {
event.preventDefault(); // امنع الافتراضي دائماً أولاً
let errors = [];
// التحقق من اسم المستخدم
let username = $("#username").val().trim();
if (username.length < 3) {
errors.push("اسم المستخدم يجب أن يكون 3 أحرف على الأقل");
}
// التحقق من البريد الإلكتروني
let email = $("#email").val();
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
errors.push("تنسيق بريد إلكتروني غير صالح");
}
// التحقق من كلمة المرور
let password = $("#password").val();
if (password.length < 8) {
errors.push("كلمة المرور يجب أن تكون 8 أحرف على الأقل");
}
// عرض الأخطاء أو الإرسال
if (errors.length > 0) {
$("#errors")
.html("<strong>أخطاء:</strong><br>" + errors.join("<br>"))
.css("color", "red");
} else {
$("#errors").html("<strong>النموذج صالح! جاري الإرسال...</strong>")
.css("color", "green");
// محاكاة إرسال AJAX
setTimeout(() => {
alert("تم إرسال النموذج بنجاح!");
this.reset();
$("#errors").html("");
}, 1000);
}
});
</script>
مثال عملي: سلوك الرابط الشرطي
معالج رابط ذكي:
<a href="https://example.com" class="smart-link">مثال</a>
<a href="https://google.com" class="smart-link">جوجل</a>
<label>
<input type="checkbox" id="openInNewTab"> فتح الروابط في تبويب جديد
</label>
<script>
$(".smart-link").on("click", function(event) {
let url = $(this).attr("href");
// Ctrl/Cmd + النقر = تبويب جديد (السلوك الافتراضي للمتصفح)
if (event.ctrlKey || event.metaKey) {
return; // السماح بالسلوك الافتراضي
}
// إذا كان مربع الاختيار محدداً، افتح في تبويب جديد
if ($("#openInNewTab").is(":checked")) {
event.preventDefault();
window.open(url, "_blank");
return;
}
// الروابط الخارجية: تأكيد قبل المغادرة
if (!url.includes(window.location.hostname)) {
event.preventDefault();
if (confirm("أنت تغادر هذا الموقع. هل تريد المتابعة؟")) {
window.location.href = url;
}
}
// الروابط الداخلية: السماح بالسلوك الافتراضي
});
</script>
بيانات الحدث المخصصة
تمرير البيانات مع الأحداث:
// إطلاق حدث ببيانات
$("#button").on("click", { user: "أحمد", role: "مدير" }, function(event) {
console.log("المستخدم:", event.data.user); // "أحمد"
console.log("الدور:", event.data.role); // "مدير"
});
// أو باستخدام trigger()
$("#target").on("customEvent", function(event, param1, param2) {
console.log("المعامل 1:", param1);
console.log("المعامل 2:", param2);
});
$("#trigger-btn").on("click", function() {
$("#target").trigger("customEvent", ["مرحبا", "عالم"]);
});
مثال عملي: معالج نقر متقدم
معالج نقر مدرك للسياق:
<div class="items">
<div class="item" data-id="1">عنصر 1</div>
<div class="item" data-id="2">عنصر 2</div>
<div class="item" data-id="3">عنصر 3</div>
</div>
<script>
$(".item").on("click", function(event) {
let $item = $(this);
let itemId = $item.data("id");
// نقرة عادية: تحديد العنصر
if (!event.shiftKey && !event.ctrlKey && !event.metaKey) {
$(".item").removeClass("selected");
$item.addClass("selected");
console.log("محدد:", itemId);
}
// Ctrl/Cmd + النقر: تبديل التحديد
else if (event.ctrlKey || event.metaKey) {
$item.toggleClass("selected");
console.log("تبديل:", itemId);
}
// Shift + النقر: تحديد نطاق
else if (event.shiftKey) {
let $selected = $(".item.selected");
if ($selected.length > 0) {
let start = $selected.index();
let end = $item.index();
let [min, max] = start < end ? [start, end] : [end, start];
$(".item").slice(min, max + 1).addClass("selected");
console.log("تحديد نطاق");
}
}
event.preventDefault(); // منع تحديد النص
});
</script>
<style>
.item {
padding: 10px;
margin: 5px 0;
background: var(--bg-light);
cursor: pointer;
user-select: none;
}
.item.selected {
background: var(--primary);
color: white;
}
</style>
ملخص طرق كائن الحدث
مرجع كامل:
$("element").on("event", function(event) {
// طرق المنع
event.preventDefault(); // إيقاف الإجراء الافتراضي للمتصفح
event.stopPropagation(); // إيقاف فقاعات الأحداث
event.stopImmediatePropagation(); // إيقاف الفقاعات + معالجات أخرى
// طرق التحقق
event.isDefaultPrevented(); // يرجع true إذا تم المنع
event.isPropagationStopped(); // يرجع true إذا تم الإيقاف
event.isImmediatePropagationStopped(); // يرجع true إذا تم الإيقاف
// الأدوات المساعدة
event.originalEvent; // كائن حدث المتصفح الأصلي
event.namespace; // مساحة اسم الحدث إذا استُخدمت
event.result; // قيمة الإرجاع من المعالج السابق
event.data; // البيانات الممررة عند ربط الحدث
});
مهم:
preventDefault() و stopPropagation() مستقلان. يمكنك استخدام واحد، كليهما، أو لا شيء حسب احتياجاتك.
اختصار Return False
استخدام "return false":
// return false = preventDefault() + stopPropagation()
$("a").on("click", function(event) {
console.log("تم النقر على الرابط");
return false; // نفس event.preventDefault() + event.stopPropagation()
});
// يعادل:
$("a").on("click", function(event) {
console.log("تم النقر على الرابط");
event.preventDefault();
event.stopPropagation();
});
أفضل ممارسة: استخدم صراحةً
preventDefault() و stopPropagation() بدلاً من return false للوضوح والتحكم الأفضل.
تمرين تطبيقي:
المهمة: إنشاء واجهة مدير ملفات متقدمة:
- عرض قائمة عناصر الملفات بأيقونات وأسماء
- نقرة واحدة: تحديد ملف (مسح التحديدات الأخرى)
- Ctrl/Cmd + النقر: تبديل تحديد الملف (تحديد متعدد)
- Shift + النقر: تحديد نطاق بين الأخير المحدد والحالي
- نقرة مزدوجة: "فتح" ملف (عرض تنبيه باسم الملف)
- النقر الأيمن: عرض قائمة سياق مخصصة (منع الافتراضي)
- خيارات قائمة السياق: فتح، إعادة تسمية، حذف، خصائص
- مفتاح Delete: حذف جميع الملفات المحددة
- عرض عدد الملفات المحددة
مكافأة: أضف السحب والإفلات لنقل الملفات. نفذ Ctrl+A لتحديد جميع الملفات. أضف تنقل لوحة المفاتيح بمفاتيح الأسهم.
النقاط الرئيسية
- كائن الحدث يُمرر كمعامل أول للمعالجات
event.preventDefault()يوقف السلوك الافتراضي للمتصفحevent.stopPropagation()يوقف فقاعات الأحداثevent.targetما أطلق،event.currentTargetما يعالجreturn false= preventDefault + stopPropagation- استخدم مفاتيح المعدل (Ctrl، Shift، Alt) للتفاعلات المتقدمة
- تحقق من خصائص الحدث لإنشاء معالجات مدركة للسياق