← الدرس السابق المستوى 3 - الدرس 7 من 10

🌲 DOM - التعامل مع الصفحة في JavaScript: الدليل الشامل

تعلم كيف تتحكم في عناصر HTML ديناميكياً باستخدام Document Object Model

تقدمك في المستوى الثالث 70%

📋 محتويات الدرس

  1. ما هو DOM وكيف يعمل؟
  2. اختيار العناصر: 5 طرق مختلفة
  3. querySelector و querySelectorAll — الأفضل
  4. تعديل المحتوى: textContent vs innerHTML
  5. تعديل الأنماط والـ CSS
  6. classList — إضافة وإزالة وtoggle للـ Classes
  7. إنشاء وإضافة عناصر جديدة
  8. حذف العناصر من الصفحة
  9. التنقل بين العناصر (Traversing)
  10. التعامل مع الخصائص (Attributes)
  11. أمثلة واقعية: عداد، قائمة مهام، نموذج
  12. اختبر فهمك — اختبار تفاعلي

📖 ما هو DOM وكيف يعمل؟

DOM (Document Object Model) هو تمثيل شجري (Tree Structure) لجميع عناصر HTML في الصفحة. يحول المتصفح كود HTML إلى شجرة من الكائنات التي يمكن لـ JavaScript التعامل معها.

💡 كيف يعمل DOM؟
  1. المتصفح يقرأ ملف HTML
  2. يُنشئ شجرة DOM في الذاكرة
  3. JavaScript تتعامل مع هذه الشجرة (تقرأ، تعدل، تضيف، تحذف)
  4. المتصفح يُعيد رسم الصفحة (Reflow/Repaint) بناءً على التغييرات

مثال من الحياة: تخيل DOM كخريطة مدينة. HTML هو المدينة نفسها، وDOM هو الخريطة المطبوعة. JavaScript هي السيارة التي تتنقل في الخريطة وتغيّر الأماكن (تُغلق متجر، تُفتتح آخر، تغيّر لون مبنى).

🔍 اختيار العناصر: 5 طرق مختلفة

JavaScript توفر عدة طرق للوصول لعناصر HTML. كل طريقة لها استخدامها الخاص:

1. getElementById — الأسرع

يُرجع عنصراً واحداً بناءً على الـ id. هذا هو الأسرع لأن id فريد في الصفحة:

// HTML: <div id="header">...</div> let header = document.getElementById("header"); console.log(header); // العنصر نفسه

2. getElementsByClassName — مجموعة من العناصر

// HTML: <div class="card">...</div> let cards = document.getElementsByClassName("card"); console.log(cards[0]); // أول عنصر console.log(cards.length); // عدد العناصر

3. getElementsByTagName — حسب نوع الوسم

let paragraphs = document.getElementsByTagName("p"); let allDivs = document.getElementsByTagName("div");

4. querySelector — الأحدث والأكثر مرونة

// يُرجع أول عنصر يطابق الـ CSS Selector let firstCard = document.querySelector(".card"); // بالـ class let submitBtn = document.querySelector("#submit"); // بالـ id let navLink = document.querySelector("nav a.active"); // selector معقد

5. querySelectorAll — جميع العناصر المطابقة

// يُرجع NodeList بجميع العناصر المطابقة let allCards = document.querySelectorAll(".card"); let allLinks = document.querySelectorAll("a[href]"); // كل الروابط // يمكن التكرار عليها مباشرة allCards.forEach(card => { card.style.border = "1px solid #ccc"; });
الطريقة ما تُرجع السرعة متى تستخدم
getElementById عنصر واحد ⚡ الأسرع عندما تعرف الـ id
getElementsByClassName HTMLCollection (حية) ⚡ سريع مجموعة بنفس الـ class
getElementsByTagName HTMLCollection ⚡ سريع جميع عناصر نوع معين
querySelector عنصر واحد ⚠️ أبطأ selectors معقدة
querySelectorAll NodeList (ثابتة) ⚠️ أبطأ جميع المطابقات + forEach

✏️ تعديل المحتوى: textContent vs innerHTML

هناك فرق جوهري بين هاتين الخاصيتين. فهم الفرق ضروري لتجنب مشاكل الأمان والأداء:

let element = document.getElementById("demo"); // textContent — النص فقط (آمن وأسرع) element.textContent = "نص جديد"; // أي HTML في النص يُعامل كنص عادي element.textContent = "<strong>نص</strong>"; // يظهر كـ: <strong>نص</strong> (ليس عريضاً) // innerHTML — يحلل HTML (أبطأ وخطير) element.innerHTML = "<strong>نص عريض</strong>"; // يظهر كـ: نص عريض (عريض فعلاً) // تغيير قيمة input document.getElementById("username").value = "أحمد";
الخاصية يحلل HTML السرعة الأمان متى تستخدم
textContent ✗ لا ⚡ سريع ✓ آمن النصوص العادية
innerHTML ✓ نعم ⚠️ أبطأ ❌ XSS Risk HTML ديناميكي فقط
🚨 خطر XSS مع innerHTML:

إذا كنت تُدخل بيانات من المستخدم في innerHTML، يمكن للمهاجم إدخال كود JavaScript خبيث! استخدم textContent دائماً للبيانات القادمة من المستخدم.

🎨 تعديل الأنماط والـ CSS

يمكنك تعديل أي خاصية CSS مباشرة من JavaScript. لكن تذكر: inline styles تتجاوز CSS الخارجي:

let box = document.querySelector(".box"); // تغيير خصائص CSS مباشرة box.style.backgroundColor = "red"; // background-color box.style.fontSize = "20px"; // font-size box.style.marginTop = "10px"; // margin-top // إخفاء وإظهار box.style.display = "none"; // إخفاء box.style.display = "block"; // إظهار (block) box.style.display = "flex"; // إظهار (flex) // تعديلات متعددة مرة واحدة Object.assign(box.style, { backgroundColor: "blue", color: "white", padding: "20px", borderRadius: "10px" });
⚠️ ملاحظة:

في JavaScript، أسماء CSS التي تحتوي على شرطة (-) تُكتب بـ camelCase: background-colorbackgroundColor, font-sizefontSize.

📋 classList — إدارة Classes بذكاء

classList هي الطريقة الحديثة والمثلى لإضافة وإزالة classes. أفضل بكثير من تعديل className يدوياً:

let box = document.querySelector(".box"); // إضافة class box.classList.add("active", "highlight"); // إزالة class box.classList.remove("hidden", "disabled"); // toggle — يضيف إن لم يكن موجوداً، ويزيل إذا موجود box.classList.toggle("visible"); // يضيف box.classList.toggle("visible"); // يزيل // toggle مع شرط box.classList.toggle("dark-mode", isNightTime); // التحقق من وجود class if (box.classList.contains("active")) { console.log("العنصر نشط!"); } // استبدال class box.classList.replace("old-class", "new-class");

➕ إنشاء وإضافة عناصر جديدة

يمكنك إنشاء عناصر HTML جديدة تماماً وإضافتها للصفحة ديناميكياً:

// 1. إنشاء العنصر let newDiv = document.createElement("div"); // 2. إضافة محتوى وخصائص newDiv.textContent = "أنا عنصر جديد!"; newDiv.className = "new-box"; newDiv.id = "dynamic-box"; newDiv.style.backgroundColor = "lightblue"; // 3. إضافة للصفحة document.body.appendChild(newDiv); // في نهاية body // append — أحدث وأفضل (يمكن إضافة نصوص وعناصر) let container = document.getElementById("container"); container.append(newDiv, " نص إضافي"); // prepend — إضافة في البداية container.prepend(newDiv); // insertBefore — إضافة قبل عنصر معين let reference = document.getElementById("reference"); container.insertBefore(newDiv, reference); // insertAdjacentElement — تحكم دقيق في الموقع reference.insertAdjacentElement("afterend", newDiv); // beforebegin, afterbegin, beforeend, afterend

🗑️ حذف العناصر من الصفحة

// الطريقة 1: remove() — الحديثة (ES6) let toDelete = document.getElementById("old"); toDelete.remove(); // ✓ بسيط ومباشر // الطريقة 2: removeChild() — القديمة let parent = document.getElementById("container"); let child = document.getElementById("old"); parent.removeChild(child); // حذف جميع أبناء عنصر let list = document.getElementById("myList"); while (list.firstChild) { list.removeChild(list.firstChild); } // innerHTML = "" — أسرع لحذف الكل list.innerHTML = ""; // ⚡ أسرع من loop

🌳 التنقل بين العناصر (Traversing)

أحياناً تحتاج للانتقال من عنصر لعنصر قريب (أب، أخ، ابن):

let item = document.querySelector(".list-item"); // الانتقال للأعلى item.parentElement; // الأب المباشر item.closest(".container"); // أقرب ancestor يطابق selector // الانتقال للأسفل item.children; // جميع الأبناء (عناصر فقط) item.firstElementChild; // أول ابن item.lastElementChild; // آخر ابن // الانتقال للجانب item.nextElementSibling; // الأخ التالي item.previousElementSibling; // الأخ السابق

🏷️ التعامل مع الخصائص (Attributes)

let link = document.querySelector("a"); // قراءة attribute link.getAttribute("href"); // "https://example.com" link.href; // نفس النتيجة (للخصائص القياسية) // تعديل attribute link.setAttribute("target", "_blank"); link.href = "https://new-url.com"; // إزالة attribute link.removeAttribute("title"); // التحقق من وجود attribute link.hasAttribute("disabled"); // true أو false // data attributes — خصائص مخصصة // HTML: <div data-user-id="123" data-role="admin"> let div = document.querySelector("[data-user-id]"); div.dataset.userId; // "123" (camelCase تلقائياً) div.dataset.role; // "admin"

💼 أمثلة واقعية من التطبيقات

1. عداد النقرات التفاعلي

// HTML: // <div class="counter"> // <span id="count">0</span> // <button id="increment">+</button> // <button id="decrement">-</button> // <button id="reset">إعادة</button> // </div> let count = 0; const countDisplay = document.getElementById("count"); const incrementBtn = document.getElementById("increment"); const decrementBtn = document.getElementById("decrement"); const resetBtn = document.getElementById("reset"); function updateDisplay() { countDisplay.textContent = count; countDisplay.classList.toggle("negative", count < 0); } incrementBtn.addEventListener("click", () => { count++; updateDisplay(); }); decrementBtn.addEventListener("click", () => { count--; updateDisplay(); }); resetBtn.addEventListener("click", () => { count = 0; updateDisplay(); });

2. قائمة مهام (To-Do List)

const taskInput = document.getElementById("taskInput"); const addBtn = document.getElementById("addBtn"); const taskList = document.getElementById("taskList"); function addTask() { const text = taskInput.value.trim(); if (!text) return; const li = document.createElement("li"); li.className = "task-item"; li.innerHTML = ` <span class="task-text">${text}</span> <button class="delete-btn">حذف</button> `; // حذف المهمة li.querySelector(".delete-btn").addEventListener("click", () => { li.remove(); }); taskList.appendChild(li); taskInput.value = ""; } addBtn.addEventListener("click", addTask); taskInput.addEventListener("keypress", (e) => { if (e.key === "Enter") addTask(); });

3. تبديل الوضع الليلي (Dark Mode)

const themeToggle = document.getElementById("themeToggle"); const body = document.body; themeToggle.addEventListener("click", () => { body.classList.toggle("dark-mode"); // حفظ التفضيل const isDark = body.classList.contains("dark-mode"); localStorage.setItem("darkMode", isDark); });

🚀 الخطوة التالية: الأحداث (Events)

في الدرس الثامن، ستتعلم كيف تتعامل مع تفاعلات المستخدم:

  • 👆 click, mouseover, mouseout
  • ⌨️ keydown, keyup, input
  • 📜 scroll, load, resize
  • 🎯 Event Delegation — أداء أفضل
📚 مصادر إضافية:
← الدرس السابق: المصفوفات والكائنات