2

So within my Javascript I am able to duplicate my HTMl Id="characters" wrapper only once. I know it should technically be a "class" rather than an "Id" because it will be a duplicated "Id", but for some reason I don't get; when I change my "getElementById" to a "getElementsByClassName" and my HTML "Id" to a "class" it doesn't duplicate at all. Also because I am using clone.Node(true), I am losing the functionality of my "addEventListeners" in the duplicated wrapper. Is there a way to correct this using only vanilla Javascript? And as if this isn't annoying enough, my duplicated wrapper is throwing itself out of my CSS grid it seems. its all very tedious and troublesome, and so I thank you for any advice I can get.

Here is my current HTML.

<!DOCTYPE html>
<html>
    <head>
        <link rel="stylesheet" href="css/style.css">
        <h1><!--D&D DM Tool--><h1>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title></title>
    </head>
    <body>
        <div id="header-container">
            <h1 id="header">Game Tools</h1>
        </div>

        <div class="importantbuttons">
            <button id="resetbutton">Next Round</button>
            <button id="orderbutton">Order</button>
            <button id="zerobutton">Zero</button>
            <button id="deadremoverbtn">Bring Out the Dead!</button>
            <button id="addnpcbtn">Add NPC</button>
        </div>

        <div id="grid"> 
            <div class="characters">
                <div id="subgrid" class="subgrid" >
                    <div class="name-bloodiedstuff">
                        <div class="halfWayDown"></div>
                        <div class="character-name-display">Name</div>
                        <button class="name-submit-btn">Submit</button>
                        <input type="text" class="input-name-el" placeholder="Name">
                        <div class=int-stuf>
                            <div class="roll-display">Iniative</div>
                            <button class="iniativebtn">Submit</button>
                            <input type="number" class="iniative-roll-el" placeholder="Iniative Roll">
                        </div>
                        <div class="healthpoints-display">Healthpoints</div>
                        <button class="hp-submit-btn">Submit</button>
                        <input type="number" class="input-hp-el" placeholder="Total HealthPoints">
                        <button class="hp-deductin-btn">Submit</button>
                        <input type="number" class="input-hp-deduction-el" placeholder="Damage">
                    </div>
                    <div class="weapons-display">Weapons</div>
                    <button class="weapon-btn">Submit</button>
                    <input type="text" class="weapon-input-el" placeholder="Weapons">
                    <button class="active-btn" class="button">Active</button>       
                </div>
            </div>
        </div>
    </body>
    <script src="mainCopy.js"></script>
</html>

Here is my current Javascript duplication function.

addNpcBtn.addEventListener("click",function(){
    var characterSubGrids=document.getElementsByClassName("characters");
    console.log(characterSubGrids[0]);
    var characterSubGridsClone=characterSubGrids[0].cloneNode(true);
    let grid=document.getElementById("grid");
    console.log(grid);
    grid.appendChild(characterSubGridsClone);
});
Shawn
  • 10,931
  • 18
  • 81
  • 126
SamBoone
  • 39
  • 7
  • getElementById returns a single (if matched) element object, whereas getElementsByClassName will return an HTMLCollection (array-like) as there could be multiple matches for class. You would need to choose which one matched element you wish to clone. You would access it just an item in an array. `list[0]`. As for the listeners you can define a named function and then reuse it multiple times as listeners for several elements. – Shreshth Mar 08 '22 at 17:23
  • 1
    Regarding appending the cloned element, you're appending it to the body. I guess you wish to append it inside `div#grid` – Shreshth Mar 08 '22 at 17:24
  • Okay, this is starting to make sense, but correct me if I'm wrong. So I need to append it to the "Id"=grid. and I need to make my div a class and select it like you would an object in an array? – SamBoone Mar 08 '22 at 17:49
  • Thank you. So I have got everything appended to the right spot and am able to duplicate my first div wrapper. Im still losing the functionality of the new divs internal buttons though. – SamBoone Mar 08 '22 at 18:13
  • I will update the code to show the progress. – SamBoone Mar 08 '22 at 18:21
  • You might want to run your HTML through an HTML validator too, I'm seeing a few non-standard things in there that might be confusing to anyone reading the code, including me, you and even browsers themselves… For example, having an `

    ` inside your `` doesn't really make too much sense. Both these tags also need to be closed with `` and `

    ` respectively. Similarly, the `
    – Shawn Mar 08 '22 at 21:00
  • Here is your HTML, but with just enough changes to make it "valid HTML": https://gist.github.com/shawninder/11817be3c1c112e3161f10665559c1b8 I used the [W3C validation service](https://validator.w3.org/) to do the validation via direct input. – Shawn Mar 08 '22 at 21:10
  • okay, I didn't see this comment until now. I will make these changes. – SamBoone Mar 09 '22 at 21:09

1 Answers1

3

From the MDN article on cloneNode

Cloning a node copies all of its attributes and their values, including intrinsic (inline) listeners. It does not copy event listeners added using addEventListener() or those assigned to element properties (e.g., node.onclick = someFunction).

It seems like cloneNode might already be ignoring the event listeners you're trying to ignore.

If you're trying to clone a <div> in a way that conserves the event listeners on its <button> children, I don't think the DOM has a method for that. Instead, you'll have to attach the same event listeners to the new cloned buttons. Something like the following:

// Fetch the container and save that element as a constant for future use
const container = document.querySelector('#container')
// Create the addHandlers function for future use
function addHandlers (span) {
  // Find the first button child of the element provided as first parameter
  const button = span.querySelector('button')
  // Add a click event listener to that button
  button.addEventListener('click', () => {
    // On click, clone the parent element (the span, not the button)
    const clone = span.cloneNode(true)
    // Find the first input child of this new clone
    const input = clone.querySelector('input')
    // Set its value to the empty string (by default, the clone would conserve the value of the original)
    input.value = ''
    // Add handlers to the clone which were lost during cloning
    addHandlers(clone)
    // Add the clone into the DOM as a new, last child of the container
    container.appendChild(clone)
  }, false)
}
// Find all elements with class name 'duplicatable' and run the `addHandlers` function on each element
document.querySelectorAll('.duplicatable').forEach(addHandlers)
<div id="container">
  <h2>Characters</h2>
  <span class="duplicatable">
    <button>Add a new character</button>
    <input type='text' placeholder='name' />
  </span>
</div>
Shawn
  • 10,931
  • 18
  • 81
  • 126
  • So I am not trying to ignore my "Event Listeners" within the duplicated div though. I am trying to get them to each run independently. – SamBoone Mar 08 '22 at 18:18
  • You can add the event handlers you want to the clone. I updated my answer to give an example. – Shawn Mar 08 '22 at 20:47
  • That seemed to do the trick! Thank you Shawn! – SamBoone Mar 09 '22 at 17:11
  • Okay so, I thought I understood, but I actually don't. My apologies, but so how is it supposed to look adding the event handlers to one of the cloned buttons? I am sorry I just don't understand. – SamBoone Mar 09 '22 at 19:00
  • The key line of code to notice in my snippet is `addHandlers(clone)`. That's the part that adds the handlers to the cloned buttons. I also use the same function to add similar handlers to the original (non-clone) buttons with the following line: `document.querySelectorAll('.duplicatable').forEach(addHandlers)`. I guess I'm not sure what's confusing you at this point. Is there perhaps a function I'm using with which you are not familiar? – Shawn Mar 10 '22 at 18:14
  • I've added comments to the code snippet in case that helps – Shawn Mar 10 '22 at 18:19
  • Thank you, I think I get it now with the comments. I was just struggling to grasp what was going on with the code. Thanks again. – SamBoone Mar 11 '22 at 18:14
  • Great :) In case it helps, here are a few links: [querySelector](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector), [querySelectorAll](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelectorAll), [addEventListener](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener), [cloneNode](https://developer.mozilla.org/en-US/docs/Web/API/Node/cloneNode), [appendChild](https://developer.mozilla.org/en-US/docs/Web/API/Node/appendChild), [forEach](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach). – Shawn Mar 12 '22 at 00:57