לדלג לתוכן

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

פתרון - יצירה ומחיקה של אלמנטים

פתרון תרגיל 1

<!DOCTYPE html>
<html lang="he" dir="rtl">
<head>
    <meta charset="UTF-8">
    <title>Create Elements</title>
</head>
<body>
    <button id="create-btn">Create Post</button>
    <div id="output"></div>

    <script>
        const output = document.getElementById("output");
        const createBtn = document.getElementById("create-btn");

        createBtn.addEventListener("click", function() {
            // create article
            const article = document.createElement("article");
            article.classList.add("post");

            // create h2
            const title = document.createElement("h2");
            title.textContent = "My First Post";

            // create meta paragraph
            const meta = document.createElement("p");
            meta.classList.add("meta");
            meta.textContent = "Published on 2024-01-15";

            // create content paragraph
            const content = document.createElement("p");
            content.classList.add("content");
            content.textContent = "This is the content of my first blog post.";

            // create link
            const link = document.createElement("a");
            link.href = "#";
            link.classList.add("read-more");
            link.textContent = "Read more";

            // assemble
            article.append(title, meta, content, link);
            output.appendChild(article);
        });
    </script>
</body>
</html>

פתרון תרגיל 2

<!DOCTYPE html>
<html lang="he" dir="rtl">
<head>
    <meta charset="UTF-8">
    <title>Shopping List</title>
    <style>
        li { padding: 5px 0; font-size: 18px; }
        .delete-btn { margin-right: 10px; cursor: pointer; color: red; border: none; background: none; font-size: 16px; }
    </style>
</head>
<body>
    <h1>Shopping List</h1>
    <input type="text" id="item-input" placeholder="Enter item...">
    <button id="add-btn">Add</button>
    <p id="counter">Items: 0</p>
    <ul id="shopping-list"></ul>

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

        function updateCounter() {
            counter.textContent = "Items: " + list.children.length;
        }

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

            // create list item
            const li = document.createElement("li");

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

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

            li.append(deleteBtn, span);
            list.appendChild(li);

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

        addBtn.addEventListener("click", addItem);

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

פתרון תרגיל 3

<!DOCTYPE html>
<html lang="he" dir="rtl">
<head>
    <meta charset="UTF-8">
    <title>Dynamic Table</title>
    <style>
        table { border-collapse: collapse; width: 100%; }
        th, td { border: 1px solid #ddd; padding: 10px; text-align: right; }
        th { background-color: #333; color: white; }
        .fail-row { background-color: #ffebee; }
    </style>
</head>
<body>
    <div id="table-container"></div>

    <script>
        const students = [
            { name: "Alice", grade: 92, status: "pass" },
            { name: "Bob", grade: 45, status: "fail" },
            { name: "Charlie", grade: 78, status: "pass" },
            { name: "Diana", grade: 88, status: "pass" },
            { name: "Eve", grade: 34, status: "fail" }
        ];

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

        // create table
        const table = document.createElement("table");

        // create thead
        const thead = document.createElement("thead");
        const headerRow = document.createElement("tr");
        const headers = ["Name", "Grade", "Status"];

        headers.forEach(function(headerText) {
            const th = document.createElement("th");
            th.textContent = headerText;
            headerRow.appendChild(th);
        });

        thead.appendChild(headerRow);
        table.appendChild(thead);

        // create tbody using DocumentFragment
        const tbody = document.createElement("tbody");
        const fragment = document.createDocumentFragment();

        students.forEach(function(student) {
            const tr = document.createElement("tr");

            if (student.status === "fail") {
                tr.classList.add("fail-row");
            }

            const tdName = document.createElement("td");
            tdName.textContent = student.name;

            const tdGrade = document.createElement("td");
            tdGrade.textContent = student.grade;

            const tdStatus = document.createElement("td");
            tdStatus.textContent = student.status;

            tr.append(tdName, tdGrade, tdStatus);
            fragment.appendChild(tr);
        });

        tbody.appendChild(fragment);
        table.appendChild(tbody);
        container.appendChild(table);
    </script>
</body>
</html>

פתרון תרגיל 4

<!DOCTYPE html>
<html lang="he" dir="rtl">
<head>
    <meta charset="UTF-8">
    <title>Move Items</title>
    <style>
        .lists { display: flex; gap: 40px; }
        ul { list-style: none; padding: 0; min-height: 200px; border: 1px solid #ddd; width: 200px; }
        li { padding: 10px; border-bottom: 1px solid #eee; cursor: pointer; }
        li:hover { background-color: #f0f0f0; }
    </style>
</head>
<body>
    <div class="lists">
        <div>
            <h3>Available</h3>
            <ul id="available">
                <li>JavaScript</li>
                <li>Python</li>
                <li>Java</li>
                <li>C++</li>
                <li>Ruby</li>
            </ul>
        </div>
        <div>
            <h3>Selected</h3>
            <ul id="selected"></ul>
        </div>
    </div>

    <script>
        const available = document.getElementById("available");
        const selected = document.getElementById("selected");

        // add click handlers to available items
        available.addEventListener("click", function(event) {
            if (event.target.tagName === "LI") {
                // moving an element to a new parent automatically removes it from the old parent
                selected.appendChild(event.target);
            }
        });

        // add click handlers to selected items
        selected.addEventListener("click", function(event) {
            if (event.target.tagName === "LI") {
                available.appendChild(event.target);
            }
        });
    </script>
</body>
</html>

כשמוסיפים אלמנט שכבר קיים ב-DOM למיקום חדש (עם appendChild או append), הוא מועבר אוטומטית - לא צריך למחוק אותו ידנית מהמיקום הישן.

פתרון תרגיל 5

<!DOCTYPE html>
<html lang="he" dir="rtl">
<head>
    <meta charset="UTF-8">
    <title>Clone Cards</title>
    <style>
        .card { border: 1px solid #ddd; border-radius: 8px; width: 220px; display: inline-block; margin: 10px; overflow: hidden; }
        .card-image { width: 100%; height: 150px; object-fit: cover; }
        .card-title { margin: 10px; }
        .card-description { margin: 10px; color: #666; }
        .card-btn { margin: 10px; padding: 8px 16px; cursor: pointer; }
    </style>
</head>
<body>
    <div id="card-template" style="display: none;">
        <div class="card">
            <img src="" alt="" class="card-image">
            <h3 class="card-title">Title</h3>
            <p class="card-description">Description</p>
            <button class="card-btn">Details</button>
        </div>
    </div>

    <div id="cards-container"></div>

    <script>
        const products = [
            { title: "Laptop", description: "Powerful laptop", image: "https://picsum.photos/200/150?random=1" },
            { title: "Phone", description: "Smart phone", image: "https://picsum.photos/200/150?random=2" },
            { title: "Tablet", description: "Light tablet", image: "https://picsum.photos/200/150?random=3" }
        ];

        const template = document.querySelector("#card-template .card");
        const container = document.getElementById("cards-container");

        products.forEach(function(product) {
            // deep clone the template
            const card = template.cloneNode(true);

            // update content
            card.querySelector(".card-title").textContent = product.title;
            card.querySelector(".card-description").textContent = product.description;

            const img = card.querySelector(".card-image");
            img.src = product.image;
            img.alt = product.title;

            // add click handler to the button
            card.querySelector(".card-btn").addEventListener("click", function() {
                alert("Details for: " + product.title);
            });

            container.appendChild(card);
        });
    </script>
</body>
</html>

פתרון תרגיל 6

<!DOCTYPE html>
<html lang="he" dir="rtl">
<head>
    <meta charset="UTF-8">
    <title>insertAdjacentHTML</title>
    <style>
        #target { border: 2px solid blue; padding: 20px; margin: 20px 0; }
        button { margin: 5px; padding: 8px 16px; }
        .added { background-color: #e3f2fd; padding: 5px; margin: 3px 0; }
    </style>
</head>
<body>
    <div id="target">
        <p>Original content</p>
    </div>
    <button id="btn-before-begin">beforebegin</button>
    <button id="btn-after-begin">afterbegin</button>
    <button id="btn-before-end">beforeend</button>
    <button id="btn-after-end">afterend</button>
    <button id="btn-reset">Reset</button>

    <script>
        const target = document.getElementById("target");
        let count = 0;

        // save original HTML for reset
        const originalHTML = target.outerHTML;
        const parent = target.parentElement;

        document.getElementById("btn-before-begin").addEventListener("click", function() {
            count++;
            target.insertAdjacentHTML("beforebegin",
                '<p class="added">beforebegin - paragraph ' + count + '</p>');
        });

        document.getElementById("btn-after-begin").addEventListener("click", function() {
            count++;
            target.insertAdjacentHTML("afterbegin",
                '<p class="added">afterbegin - paragraph ' + count + '</p>');
        });

        document.getElementById("btn-before-end").addEventListener("click", function() {
            count++;
            target.insertAdjacentHTML("beforeend",
                '<p class="added">beforeend - paragraph ' + count + '</p>');
        });

        document.getElementById("btn-after-end").addEventListener("click", function() {
            count++;
            target.insertAdjacentHTML("afterend",
                '<p class="added">afterend - paragraph ' + count + '</p>');
        });

        document.getElementById("btn-reset").addEventListener("click", function() {
            // remove all added elements before and after target
            const added = document.querySelectorAll(".added");
            added.forEach(function(el) {
                el.remove();
            });

            // restore original content
            target.innerHTML = "<p>Original content</p>";
            count = 0;
        });
    </script>
</body>
</html>

פתרון תרגיל 7

<!DOCTYPE html>
<html lang="he" dir="rtl">
<head>
    <meta charset="UTF-8">
    <title>Contact Manager</title>
    <style>
        body { font-family: Arial, sans-serif; padding: 20px; max-width: 600px; margin: 0 auto; }
        .form-group { margin: 10px 0; }
        .form-group input { padding: 8px; width: 200px; }
        .contact-card { border: 1px solid #ddd; border-radius: 8px; padding: 15px; margin: 10px 0; }
        .contact-card h3 { margin: 0 0 10px 0; }
        .contact-card p { margin: 5px 0; color: #555; }
        .btn { padding: 6px 12px; margin-left: 5px; cursor: pointer; border: 1px solid #ddd; border-radius: 4px; }
        .btn-delete { background-color: #f44336; color: white; border: none; }
        .btn-edit { background-color: #2196F3; color: white; border: none; }
    </style>
</head>
<body>
    <h1>Contact Manager</h1>

    <div id="form">
        <div class="form-group">
            <input type="text" id="name-input" placeholder="Name">
        </div>
        <div class="form-group">
            <input type="tel" id="phone-input" placeholder="Phone">
        </div>
        <div class="form-group">
            <input type="email" id="email-input" placeholder="Email">
        </div>
        <button id="add-btn" class="btn">Add Contact</button>
    </div>

    <p id="counter">Contacts: 0</p>
    <div id="contacts-container"></div>

    <script>
        const nameInput = document.getElementById("name-input");
        const phoneInput = document.getElementById("phone-input");
        const emailInput = document.getElementById("email-input");
        const addBtn = document.getElementById("add-btn");
        const container = document.getElementById("contacts-container");
        const counter = document.getElementById("counter");

        function updateCounter() {
            const count = container.children.length;
            counter.textContent = "Contacts: " + count;
        }

        function createContactCard(name, phone, email) {
            const card = document.createElement("div");
            card.classList.add("contact-card");

            const nameEl = document.createElement("h3");
            nameEl.textContent = name;

            const phoneEl = document.createElement("p");
            phoneEl.textContent = "Phone: " + phone;

            const emailEl = document.createElement("p");
            emailEl.textContent = "Email: " + email;

            const buttonsDiv = document.createElement("div");

            const editBtn = document.createElement("button");
            editBtn.textContent = "Edit";
            editBtn.classList.add("btn", "btn-edit");
            editBtn.addEventListener("click", function() {
                const newName = prompt("Enter new name:", nameEl.textContent);
                if (newName && newName.trim() !== "") {
                    nameEl.textContent = newName.trim();
                }
            });

            const deleteBtn = document.createElement("button");
            deleteBtn.textContent = "Delete";
            deleteBtn.classList.add("btn", "btn-delete");
            deleteBtn.addEventListener("click", function() {
                if (confirm("Are you sure you want to delete " + nameEl.textContent + "?")) {
                    card.remove();
                    updateCounter();
                }
            });

            buttonsDiv.append(editBtn, deleteBtn);
            card.append(nameEl, phoneEl, emailEl, buttonsDiv);

            return card;
        }

        addBtn.addEventListener("click", function() {
            const name = nameInput.value.trim();
            const phone = phoneInput.value.trim();
            const email = emailInput.value.trim();

            if (name === "" || phone === "" || email === "") {
                alert("Please fill in all fields.");
                return;
            }

            const card = createContactCard(name, phone, email);
            container.appendChild(card);

            // clear inputs
            nameInput.value = "";
            phoneInput.value = "";
            emailInput.value = "";
            nameInput.focus();

            updateCounter();
        });
    </script>
</body>
</html>

פתרון תרגיל 8

const data = [
    { title: "Item 1", description: "Description for item 1", tags: ["new", "sale"] },
    { title: "Item 2", description: "Description for item 2", tags: ["popular"] },
    { title: "Item 3", description: "Description for item 3", tags: ["new"] }
];

// version A: innerHTML
function renderWithInnerHTML(data) {
    let html = "";
    data.forEach(function(item) {
        const tagsHTML = item.tags
            .map(function(tag) { return '<span class="tag">' + tag + '</span>'; })
            .join("");

        html += `
            <div class="item">
                <h3>${item.title}</h3>
                <p>${item.description}</p>
                <div class="tags">${tagsHTML}</div>
            </div>
        `;
    });
    document.getElementById("container-a").innerHTML = html;
}

// version B: createElement
function renderWithCreateElement(data) {
    const container = document.getElementById("container-b");
    container.innerHTML = ""; // clear existing content

    const fragment = document.createDocumentFragment();

    data.forEach(function(item) {
        const itemDiv = document.createElement("div");
        itemDiv.classList.add("item");

        const title = document.createElement("h3");
        title.textContent = item.title;

        const desc = document.createElement("p");
        desc.textContent = item.description;

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

        item.tags.forEach(function(tag) {
            const span = document.createElement("span");
            span.classList.add("tag");
            span.textContent = tag;
            tagsDiv.appendChild(span);
        });

        itemDiv.append(title, desc, tagsDiv);
        fragment.appendChild(itemDiv);
    });

    container.appendChild(fragment);
}

השוואה:
- innerHTML - יותר קצר ויותר קל לקרוא, אבל מסוכן אם הנתונים מגיעים ממשתמש (XSS)
- createElement - יותר ארוך אבל בטוח לגמרי, כי textContent מתייחס לכל דבר כטקסט רגיל
- במקרה הזה הנתונים הם hardcoded אז שתי הגרסאות בטוחות, אבל בפרויקט אמיתי שבו הנתונים מגיעים מהמשתמש או מ-API, עדיף createElement

תשובות לשאלות

  1. appendChild מול append - appendChild מקבל רק אלמנט אחד ומחזיר אותו. append מקבל כמה אלמנטים ומחרוזות, ולא מחזיר ערך. append יותר גמיש ומודרני.

  2. innerHTML מסוכן עם קלט ממשתמש כי אם המשתמש מכניס HTML עם JavaScript (למשל <script> או <img onerror="...">), הקוד ירוץ בדפדפן. זו התקפת XSS (Cross-Site Scripting). עם textContent או createElement, הטקסט לא מפורש כ-HTML.

  3. cloneNode(true) מול cloneNode(false) - cloneNode(true) שכפול עמוק שכולל את כל הצאצאים. cloneNode(false) שכפול רדוד שמשכפל רק את האלמנט עצמו, ללא ילדים. שכפול לא משכפל event listeners.

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

  5. הזזת אלמנט קיים - כשמוסיפים אלמנט שכבר קיים ב-DOM למיקום חדש (עם appendChild, append וכו'), הוא מועבר אוטומטית מהמיקום הישן למיקום החדש. אלמנט יכול להיות רק במקום אחד ב-DOM.