לדלג לתוכן

3.3 יצירה ומחיקה של אלמנטים הרצאה

יצירה של אלמנטים

עד עכשיו למדנו לבחור אלמנטים קיימים ולשנות אותם. עכשיו נלמד ליצור אלמנטים חדשים מאפס ולהוסיף אותם ל-DOM, וגם למחוק אלמנטים שלא צריכים יותר.

createElement - יצירת אלמנט חדש

המתודה document.createElement יוצרת אלמנט HTML חדש:

// create a new paragraph
const paragraph = document.createElement("p");
console.log(paragraph); // <p></p> (empty element, not in DOM yet)

// create a div
const div = document.createElement("div");

// create a button
const button = document.createElement("button");

// create an image
const img = document.createElement("img");
  • האלמנט נוצר בזיכרון, אבל הוא עדיין לא בדף
  • צריך להוסיף אותו ל-DOM כדי שהוא יופיע

הגדרת תוכן ותכונות על אלמנט חדש

אחרי שיצרנו אלמנט, נוסיף לו תוכן ותכונות:

// create and configure a paragraph
const paragraph = document.createElement("p");
paragraph.textContent = "This is a new paragraph.";
paragraph.classList.add("intro", "highlight");
paragraph.id = "my-paragraph";

// create and configure a link
const link = document.createElement("a");
link.href = "https://google.com";
link.textContent = "Go to Google";
link.target = "_blank";
link.classList.add("external-link");

// create and configure an image
const img = document.createElement("img");
img.src = "photo.jpg";
img.alt = "A nice photo";
img.width = 300;

// create and configure an input
const input = document.createElement("input");
input.type = "text";
input.placeholder = "Enter your name";
input.id = "name-input";
input.required = true;

// create and configure a button
const button = document.createElement("button");
button.textContent = "Click me!";
button.classList.add("btn", "btn-primary");
button.dataset.action = "submit";

הוספת אלמנטים ל-DOM

יש כמה דרכים להוסיף אלמנט שיצרנו לעץ ה-DOM.

appendChild - הוספה בסוף

<div id="container">
    <p>Existing paragraph</p>
</div>
const container = document.getElementById("container");

const newParagraph = document.createElement("p");
newParagraph.textContent = "Added at the end.";

container.appendChild(newParagraph);
// result:
// <div id="container">
//     <p>Existing paragraph</p>
//     <p>Added at the end.</p>
// </div>
  • appendChild מוסיף את האלמנט כילד אחרון
  • הוא מחזיר את האלמנט שנוסף

append - גרסה מודרנית ויותר גמישה

const container = document.getElementById("container");

// append accepts multiple arguments
const p1 = document.createElement("p");
p1.textContent = "First new paragraph";

const p2 = document.createElement("p");
p2.textContent = "Second new paragraph";

// add multiple elements at once
container.append(p1, p2);

// append can also accept plain strings
container.append("Some plain text at the end");
  • append יכול לקבל כמה אלמנטים בו-זמנית
  • append יכול לקבל גם מחרוזות טקסט (לא רק אלמנטים)
  • append לא מחזיר ערך (לעומת appendChild שמחזיר את האלמנט)

prepend - הוספה בהתחלה

const container = document.getElementById("container");

const notice = document.createElement("p");
notice.textContent = "This will be the first child!";
notice.style.fontWeight = "bold";

container.prepend(notice);
// the notice is now the FIRST child of container

before ו-after - הוספה לפני או אחרי אלמנט

<div id="container">
    <p id="middle">Middle paragraph</p>
</div>
const middle = document.getElementById("middle");

// add element BEFORE the middle paragraph
const before = document.createElement("p");
before.textContent = "I come before!";
middle.before(before);

// add element AFTER the middle paragraph
const after = document.createElement("p");
after.textContent = "I come after!";
middle.after(after);

// result:
// <div id="container">
//     <p>I come before!</p>
//     <p id="middle">Middle paragraph</p>
//     <p>I come after!</p>
// </div>

insertBefore - הוספה לפני אלמנט ספציפי (דרך ההורה)

<ul id="list">
    <li>Item 1</li>
    <li id="item-3">Item 3</li>
</ul>
const list = document.getElementById("list");
const item3 = document.getElementById("item-3");

// create item 2
const item2 = document.createElement("li");
item2.textContent = "Item 2";

// insert item 2 before item 3
list.insertBefore(item2, item3);

// result:
// <ul>
//     <li>Item 1</li>
//     <li>Item 2</li>
//     <li id="item-3">Item 3</li>
// </ul>
  • insertBefore נקרא על ההורה, ומקבל שני פרמטרים: האלמנט החדש, והאלמנט שלפניו להוסיף

insertAdjacentHTML - הוספת HTML כמחרוזת

מתודה שמאפשרת להוסיף HTML כמחרוזת בארבעה מיקומים שונים:

<div id="target">
    <p>Existing content</p>
</div>
const target = document.getElementById("target");

// beforebegin - before the element itself
target.insertAdjacentHTML("beforebegin", "<p>Before the div</p>");

// afterbegin - inside the element, before first child
target.insertAdjacentHTML("afterbegin", "<p>First inside the div</p>");

// beforeend - inside the element, after last child
target.insertAdjacentHTML("beforeend", "<p>Last inside the div</p>");

// afterend - after the element itself
target.insertAdjacentHTML("afterend", "<p>After the div</p>");

התוצאה:

<p>Before the div</p>
<div id="target">
    <p>First inside the div</p>
    <p>Existing content</p>
    <p>Last inside the div</p>
</div>
<p>After the div</p>

ארבעת המיקומים:

<!-- beforebegin -->
<div id="target">
    <!-- afterbegin -->
    <p>Existing content</p>
    <!-- beforeend -->
</div>
<!-- afterend -->

  • שימושי כשיש לנו HTML מוכן כמחרוזת
  • יותר מהיר מ-innerHTML כי הוא לא מוחק ובונה מחדש את כל התוכן
  • עדיין יש סיכון XSS אם המחרוזת מכילה קלט ממשתמש

מחיקת אלמנטים

remove - הדרך המודרנית

<div id="container">
    <p id="to-remove">Remove me!</p>
    <p>I stay.</p>
</div>
const toRemove = document.getElementById("to-remove");
toRemove.remove(); // gone!

// result:
// <div id="container">
//     <p>I stay.</p>
// </div>
  • פשוט וקל - קוראים ל-remove() על האלמנט שרוצים להסיר

removeChild - הדרך הישנה (דרך ההורה)

const container = document.getElementById("container");
const toRemove = document.getElementById("to-remove");

container.removeChild(toRemove);
  • צריך לגשת להורה ולקרוא לו removeChild
  • remove() יותר פשוט ומומלץ

מחיקת כל הילדים

const container = document.getElementById("container");

// option 1: set innerHTML to empty string
container.innerHTML = "";

// option 2: loop and remove
while (container.firstChild) {
    container.removeChild(container.firstChild);
}

// option 3: replaceChildren with no arguments
container.replaceChildren();

החלפת אלמנטים

replaceWith - החלפת אלמנט בחדש

<div id="container">
    <p id="old">Old content</p>
</div>
const oldElement = document.getElementById("old");

const newElement = document.createElement("h2");
newElement.textContent = "New and improved!";
newElement.style.color = "green";

oldElement.replaceWith(newElement);

// result:
// <div id="container">
//     <h2 style="color: green;">New and improved!</h2>
// </div>

replaceChild - הדרך הישנה (דרך ההורה)

const container = document.getElementById("container");
const oldElement = document.getElementById("old");

const newElement = document.createElement("h2");
newElement.textContent = "New and improved!";

container.replaceChild(newElement, oldElement);

שכפול אלמנטים - cloneNode

<div class="template-card">
    <h3>Card Title</h3>
    <p>Card content goes here.</p>
    <button>Click me</button>
</div>
const template = document.querySelector(".template-card");

// shallow clone - only the element itself, without children
const shallowClone = template.cloneNode(false);
console.log(shallowClone.innerHTML); // "" (empty)

// deep clone - the element and ALL its descendants
const deepClone = template.cloneNode(true);
console.log(deepClone.innerHTML); // includes h3, p, and button

// modify the clone
deepClone.querySelector("h3").textContent = "Cloned Card";
deepClone.querySelector("p").textContent = "This is a clone!";

// add the clone to the DOM
document.body.appendChild(deepClone);
  • cloneNode(false) - שכפול רדוד, רק האלמנט עצמו
  • cloneNode(true) - שכפול עמוק, כולל כל הילדים
  • השכפול לא משכפל event listeners - צריך להוסיף אותם מחדש

DocumentFragment - שיפור ביצועים

כל פעם שאנחנו מוסיפים אלמנט ל-DOM, הדפדפן צריך לחשב מחדש את הפריסה ולצייר את הדף. אם מוסיפים הרבה אלמנטים אחד-אחד, זה יכול להיות איטי.

DocumentFragment הוא "מיכל" זמני שמאפשר לנו לבנות מבנה שלם ולהוסיף אותו ל-DOM בפעולה אחת:

// BAD - adding elements one by one (many DOM updates)
const list = document.getElementById("list");
for (let i = 0; i < 100; i++) {
    const li = document.createElement("li");
    li.textContent = "Item " + (i + 1);
    list.appendChild(li); // DOM update on each iteration!
}

// GOOD - using DocumentFragment (one DOM update)
const list2 = document.getElementById("list2");
const fragment = document.createDocumentFragment();

for (let i = 0; i < 100; i++) {
    const li = document.createElement("li");
    li.textContent = "Item " + (i + 1);
    fragment.appendChild(li); // adding to fragment (no DOM update)
}

list2.appendChild(fragment); // ONE DOM update for all 100 items
  • ה-fragment הוא מיכל זמני שלא מופיע ב-DOM
  • כשמוסיפים fragment ל-DOM, רק הילדים שלו נוספים (לא ה-fragment עצמו)
  • שיפור ביצועים משמעותי כשמוסיפים הרבה אלמנטים

innerHTML מול createElement - ביצועים ואבטחה

שתי גישות שונות להוספת תוכן:

גישת innerHTML

const container = document.getElementById("container");

// quick and easy
container.innerHTML = `
    <div class="card">
        <h2>Card Title</h2>
        <p>Card content.</p>
        <button class="btn">Click</button>
    </div>
`;

יתרונות:
- קצר ונוח לכתיבה
- קל לקרוא - רואים את ה-HTML ישירות

חסרונות:
- סיכון אבטחה (XSS) כשמכניסים קלט ממשתמש
- מוחק את כל התוכן הקיים ובונה מחדש
- מאבד event listeners על אלמנטים קיימים

// XSS DANGER! Never do this with user input:
const userInput = '<img src="x" onerror="alert(\'hacked!\')">';

// THIS IS DANGEROUS:
container.innerHTML = userInput; // the script will execute!

// THIS IS SAFE:
container.textContent = userInput; // displayed as plain text

גישת createElement

const container = document.getElementById("container");

const card = document.createElement("div");
card.classList.add("card");

const title = document.createElement("h2");
title.textContent = "Card Title";

const content = document.createElement("p");
content.textContent = "Card content.";

const button = document.createElement("button");
button.classList.add("btn");
button.textContent = "Click";

card.append(title, content, button);
container.appendChild(card);

יתרונות:
- בטוח מ-XSS - textContent מתייחס לכל דבר כטקסט רגיל
- שליטה מלאה על כל אלמנט
- לא מוחק event listeners קיימים

חסרונות:
- יותר ארוך ומפורט
- קשה יותר לקרוא כשיש מבנה מורכב

הכלל: השתמשו ב-innerHTML רק כשהנתונים בטוחים (לא קלט ממשתמש). כשיש קלט ממשתמש, השתמשו ב-createElement ו-textContent.

דוגמה מעשית - בניית רשימה דינמית

נבנה רשימת משימות פשוטה שמשתמשת בכל מה שלמדנו:

<!DOCTYPE html>
<html lang="he" dir="rtl">
<head>
    <meta charset="UTF-8">
    <title>Todo List</title>
    <style>
        body { font-family: Arial, sans-serif; padding: 20px; max-width: 500px; margin: 0 auto; }
        .todo-item {
            display: flex;
            justify-content: space-between;
            align-items: center;
            padding: 10px;
            margin: 5px 0;
            border: 1px solid #ddd;
            border-radius: 5px;
        }
        .todo-item.done { background-color: #e8f5e9; text-decoration: line-through; color: gray; }
        .delete-btn { background: #f44336; color: white; border: none; padding: 5px 10px; cursor: pointer; border-radius: 3px; }
        .done-btn { background: #4CAF50; color: white; border: none; padding: 5px 10px; cursor: pointer; border-radius: 3px; margin-left: 5px; }
        #add-input { padding: 8px; width: 70%; }
        #add-btn { padding: 8px 16px; }
    </style>
</head>
<body>
    <h1>Todo List</h1>

    <div>
        <input type="text" id="add-input" placeholder="Enter a new task...">
        <button id="add-btn">Add</button>
    </div>

    <div id="todo-list"></div>
    <p id="counter">Tasks: 0</p>

    <script>
        const input = document.getElementById("add-input");
        const addBtn = document.getElementById("add-btn");
        const todoList = document.getElementById("todo-list");
        const counter = document.getElementById("counter");

        function updateCounter() {
            const total = todoList.children.length;
            const done = todoList.querySelectorAll(".done").length;
            counter.textContent = "Tasks: " + total + " | Done: " + done;
        }

        function createTodoItem(text) {
            // create the container
            const item = document.createElement("div");
            item.classList.add("todo-item");

            // create the text span
            const textSpan = document.createElement("span");
            textSpan.textContent = text;

            // create buttons container
            const buttonsDiv = document.createElement("div");

            // create done button
            const doneBtn = document.createElement("button");
            doneBtn.textContent = "Done";
            doneBtn.classList.add("done-btn");
            doneBtn.addEventListener("click", function() {
                item.classList.toggle("done");
                updateCounter();
            });

            // create delete button
            const deleteBtn = document.createElement("button");
            deleteBtn.textContent = "Delete";
            deleteBtn.classList.add("delete-btn");
            deleteBtn.addEventListener("click", function() {
                item.remove();
                updateCounter();
            });

            // assemble the item
            buttonsDiv.append(doneBtn, deleteBtn);
            item.append(textSpan, buttonsDiv);

            return item;
        }

        function addTodo() {
            const text = input.value.trim();
            if (text === "") return;

            const todoItem = createTodoItem(text);
            todoList.appendChild(todoItem);

            input.value = "";
            input.focus();
            updateCounter();
        }

        addBtn.addEventListener("click", addTodo);

        input.addEventListener("keydown", function(event) {
            if (event.key === "Enter") {
                addTodo();
            }
        });
    </script>
</body>
</html>

הדוגמה הזו מדגימה:
- createElement ליצירת אלמנטים חדשים
- textContent להגדרת טקסט בטוח
- classList לניהול classes
- append להרכבת מבנה מקונן
- appendChild להוספה ל-DOM
- remove למחיקה מה-DOM
- addEventListener לטיפול בלחיצות ומקלדת
- children.length ו-querySelectorAll לספירה

סיכום

יצירה

מתודה תיאור
document.createElement("tag") יצירת אלמנט חדש
document.createDocumentFragment() יצירת fragment לביצועים
element.cloneNode(deep) שכפול אלמנט קיים

הוספה

מתודה תיאור
parent.appendChild(child) הוספה כילד אחרון
parent.append(...nodes) הוספה בסוף (גמיש)
parent.prepend(...nodes) הוספה בהתחלה
element.before(...nodes) הוספה לפני האלמנט
element.after(...nodes) הוספה אחרי האלמנט
parent.insertBefore(new, ref) הוספה לפני אלמנט ספציפי
element.insertAdjacentHTML(pos, html) הוספת HTML כמחרוזת

מחיקה והחלפה

מתודה תיאור
element.remove() מחיקת האלמנט
parent.removeChild(child) מחיקה דרך ההורה
element.replaceWith(new) החלפה באלמנט חדש
parent.replaceChild(new, old) החלפה דרך ההורה
  • תמיד העדיפו createElement + textContent על פני innerHTML עם קלט ממשתמש
  • השתמשו ב-DocumentFragment כשמוסיפים הרבה אלמנטים
  • הגרסאות המודרניות (append, prepend, before, after, remove, replaceWith) יותר נוחות לשימוש