简体   繁体   中英

Detecting HTML changes when adding elements with JavaScript

I'm am having a issue where I have a click event.

This is the code:

for (const button of itemClick) button.addEventListener("click", function() {
    clickCount++;
    if (clickCount == 1) {
        value1 = button.getAttribute("value");

    }
    if (clickCount >= 2) {
        value2 = button.getAttribute("value");
        clickCount = 0;
        onItemClick();
    }
});

itemClick refers to a document classname variable called item

So if I click twice on that item it should add a second item which works but clicking on that item created by JavaScript doesn't want to work so I have to somehow let JavaScript know the HTML has changes so when I click on it it also has effect but I don't know how to do that and can't find any information for it.

I am adding the element in HTML like this:

let itemDiv = document.createElement("div");
            let imageDiv = document.createElement("div");
            let imgEl = document.createElement("img");
            let itHeading = document.createElement("h6");

            let itemslistcontent = document.getElementById("items");

            itemslistcontent.appendChild(itemDiv);
            itemDiv.appendChild(imageDiv);
            imageDiv.appendChild(imgEl);
            itemDiv.appendChild(itHeading);
            itemDiv.classList.add("item");
            itemDiv.setAttribute("value", prop);
            itHeading.innerHTML = prop;

This is the full function:

function onItemClick() {
    for (var prop in itemNames) {
        if (itemNames[prop].includes(value1) && itemNames[prop].includes(value2)) {
            value1 = "";
            value2 = "";
            let itemDiv = document.createElement("div");
            let imageDiv = document.createElement("div");
            let imgEl = document.createElement("img");
            let itHeading = document.createElement("h6");

            let itemslistcontent = document.getElementById("items");

            itemslistcontent.appendChild(itemDiv);
            itemDiv.appendChild(imageDiv);
            imageDiv.appendChild(imgEl);
            itemDiv.appendChild(itHeading);
            itemDiv.classList.add("item");
            itemDiv.setAttribute("value", prop);
            itHeading.innerHTML = prop;


            console.log(itemslistcontent);
        } else {
            value1 = "";
            value2 = "";
        }
    }
}

When you add new elements to the DOM programmatically, those elements are called dynamic.

Since your code is already run when the dynamic elements are added to the DOM, the event listeners are only registered to the elements which were already present at the time of code execution.

Thus, you need to add the event listener again to the dynamic component.

Also, why don't you make use of the dblclick event in JavaScript?

Try the following code, its adding the even listener as and when the dynamic elements are created.

function onItemClick() {
    for (var prop in itemNames) {
        if (itemNames[prop].includes(value1) && itemNames[prop].includes(value2)) {
            value1 = "";
            value2 = "";
            let itemDiv = document.createElement("div");
            let imageDiv = document.createElement("div");
            let imgEl = document.createElement("img");
            let itHeading = document.createElement("h6");

            let itemslistcontent = document.getElementById("items");

            itemslistcontent.appendChild(itemDiv);
            itemDiv.appendChild(imageDiv);
            imageDiv.appendChild(imgEl);
            itemDiv.appendChild(itHeading);
            itemDiv.classList.add("item");
            itemDiv.setAttribute("value", prop);
            itHeading.innerHTML = prop;


            console.log(itemslistcontent);

            // adding the event listener

            itemDiv.addEventListener("dblclick", function() {

                value2 = button.getAttribute("value");
                clickCount = 0;
                onItemClick();
            }); 
        } else {
            value1 = "";
            value2 = "";
        }
    }
}

Event Delegation

The easiest way to track any event (ie "click" ) on any element of any amount, whether dynamically added or not, is to use Event Delegation .

  • Register an element that will or is containing all of the buttons we want to monitor/control by way of the "click" event.

     // Event Property document.querySelector('main').onclick = clickHandler;

    OR

    // EventListener document.querySelector('main').addEventListener('click', clickHandler);
  • Whenever this parent element is clicked function clickHandler() will run. This event handler (just a more descriptive name for a function triggered by an event) will delegate the event to the exact button the user clicked by:

    • using the event.target property to reference the element the user clicked.

    • narrow it down by the use of if/if else/else conditions, and the .matches() method.

     if (event.target.matches('button')) {...

Advantages

  • We only need to register events to a single parent element . window and document could be used but it's better to use an element further down like <main> or <form> IMO. This parent element can be referenced using event.currentTarget property.

     const listener = event.currentTarget; console.log(listener.tagName); // MAIN
  • Any descendant element of the event.currentTarget can be selected . If the user clicked an element (ie <button> ) then it can be referenced directly with the event.target property. If the desired element isn't event.target , but it is it's proximity, we can reference it indirectly many ways.

     <main> <section class='group'> <figure class='item'> <img src='pix.jpg'> <figcaption> <input> <button>X</button> <label>XXXX</label>... const clicked = event.target; if (clicked.matches('button')) { let group = clicked.closest('.group'); let item = clicked.closest('.item'); let tag = clicked.nextElementSibling; let txt = clicked.previousElementSibling; ... /* Reference to the <section>, <figure>, <input>, and <label> and finding the exact element clicked by user */
  • This delegation also includes any dynamically added elements as well. A common mistake newbies make is expecting elements added to the DOM are clickable, but aren't because they need to be registered to the event (ie .onclick or .addEventListener('click') ). This is never a concern using event delegation. Add whatever and whenever to the event.currentTarget; and nothing more.

Demo

 const main = document.querySelector('main'); main.onclick = clickHandler; function clickHandler(event) { const listener = event.currentTarget; const clicked = event.target; let grpIdx = indexNodes('.group'); let itmIdx = indexNodes('.item'); const itemHTMLString = ` <figure class='item'> <img src="https://placeimg.com/100/100/any"> <figcaption> <b>Item ${itmIdx+1}</b><button>&#128128;</button> <output class='count'>0</output> </figcaption> </figure>`; const groupHTMLString = ` <section class='group'> <fieldset> <legend>Group ${grpIdx+1} Total Clicks</legend> <output class='total'>1</output> </fieldset> <figure class='item'> <img src="https://placeimg.com/100/100/any"> <figcaption> <b>Item ${itmIdx+1}</b><button>&#128128;</button> <output class='count'>1</output> </figcaption> </figure> </section>`; let item, group, count, total; if (clicked.matches('button')) { item = clicked.closest('.item'); group = clicked.closest('.group'); count = clicked.nextElementSibling; total = group.querySelector('.total'); let cnt = parseInt(count.textContent, 10); let ttl = parseInt(total.textContent, 10); if (ttl < 3) { total.textContent = ttl + 1; count.textContent = cnt + 1; group.insertAdjacentHTML('beforeend', itemHTMLString); indexNodes('.group'); indexNodes('.item'); } else if (ttl === 3) { let buttons = group.querySelectorAll('button'); for (let btn of buttons) { btn.disabled = true; } listener.insertAdjacentHTML('beforeend', groupHTMLString); indexNodes('.group'); indexNodes('.item'); } else { return false; } } else { return false; } } function indexNodes(selector) { const nodes = document.querySelectorAll(selector); nodes.forEach((node, index) => node.dataset.idx = index); return nodes.length; }
 * { margin: 0; padding: 0; } main { margin: 10px auto; padding: 8px; }.group { display: flex; flex-flow: row nowrap; justify-content: flex-start; margin: 8px auto; padding: 4px; } fieldset { margin-top: -20px } legend { font-size: large; font-weight: bold; } figcaption { display: flex; flex-flow: row nowrap; justify-content: flex-end; } button, b, output { display: block; font-size: large; } b { text-align: left; padding: 8px 8px 0 8px; font-size: small; }.count { padding: 8px 8px 0 8px; }.total { margin: 0 auto; text-align: center; } button { cursor: pointer; max-height: 28px; }
 <:DOCTYPE html> <html lang='en'> <head> <meta charset='utf-8'> </head> <body> <main> <section class='group' data-idx='0'> <fieldset> <legend>Group 1 Total Clicks</legend> <output class='total'>1</output> </fieldset> <figure class='item' data-idx='0'> <img src="https.//placeimg;com/100/100/any"> <figcaption> <b>Item 1</b><button>&#128128;</button> <output class='count'>1</output> </figcaption> </figure> </section> </main> </body> </html>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM