Please help: cloneNode attaching to wrong <div>

Hello ,
Here is my ‘almost work’ example:
https://vmars.us/Guitar/GuitarScales-FORUM.html
Node gets attached to "div id=“two” ’ instead of to ‘div id=“container”’ .
I’ve tried all kinds of combinations , but so far no-go .
I’ve given up on several approaches . Please help !
When I get it working it will look like this:
https://vmars.us/Guitar/GuitarScales-Beginning.html

<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no" />
<title>Clone Elements</title>
<style>
    body {
      margin: 20px;
    }
    #container {
      width: 1375px;
      height: 450px;
      background-color: #DCDCDC;
      display: flex;
      align-items: center;
      justify-content: center;
      overflow: hidden;
      border-radius: 7px;
      touch-action: none;
      z-index: 0
    }

.tr{
      height: 55px;
}

    .item {
      border-radius: 50%;
      touch-action: none;
      user-select: none;
      position: relative;
    }
	
    .two {
      width: 32px;
      height: 32px;
      background-color: #F5F5F5;  //  whitesmoke 
	  font-family: Arial, Helvetica, sans-serif;
	  text-align:center;
	  font-size:28px;
      top: -40%;
      left: -10%;
      z-index: 2
    }

#p1 {
    position: absolute;
    top: 275px;
    left: 50px;
	width: 90%;
	border-style: solid;
}
    .item:active {
      opacity: .75;
    }

    .item:hover {
      cursor: pointer;
    }
    
    h1 {
      margin-bottom: 10px;
    }
  </style>
</head>
<body>
<!--  <h1>Drag Multiple Elements</h1>  -->
<div id="outerContainer">
<div id="container">
<br> <!--  <div id="myNote"  onclick="createNewNote()" class="circle"; 
<div id="myNote"  onclick="createNewNote()" class="circle"; 

   -->
<div class="item two" id="two" onclick="cloneTwo()" >2</div>

</div>  <!--  id="container"  -->
</div>  <!--  id="outerContainer  -->

<!--  Console-error says: "two.cloneNode is not a function:
                            and:  "Cannot read properties of null (reading 'cloneNode')"
  -->

<script>
//const init = 
function cloneTwo() {
    let  t2 ;
	let temp ;
    let divTwo
    let cln
    t2 = document.getElementById('two');
    
    divTwo = document.querySelector('two');
        t2.appendChild(two.cloneNode(true) );
    //}
    
    temp = document.querySelector('[type="text/html"]');
    cln = temp.cloneNode(true);
	
    divTwo = cln.textContent;
    t2.innerHTML = divTwo;
}

//  document.addEventListener('DOMContentLoaded', init) 
</script>

<script>
    var container = document.querySelector("#container");
    var activeItem = null;

    var active = false;

    container.addEventListener("touchstart", dragStart, false);
    container.addEventListener("touchend", dragEnd, false);
    container.addEventListener("touchmove", drag, false);

    container.addEventListener("mousedown", dragStart, false);
    container.addEventListener("mouseup", dragEnd, false);
    container.addEventListener("mousemove", drag, false);

    function dragStart(e) {

      if (e.target !== e.currentTarget) {
        active = true;

        // this is the item we are interacting with
        activeItem = e.target;

        if (activeItem !== null) {
          if (!activeItem.xOffset) {
            activeItem.xOffset = 0;
          }

          if (!activeItem.yOffset) {
            activeItem.yOffset = 0;
          }

          if (e.type === "touchstart") {
            activeItem.initialX = e.touches[0].clientX - activeItem.xOffset;
            activeItem.initialY = e.touches[0].clientY - activeItem.yOffset;
          } else {
            console.log("doing something!");
            activeItem.initialX = e.clientX - activeItem.xOffset;
            activeItem.initialY = e.clientY - activeItem.yOffset;
          }
        }
      }
    }

    function dragEnd(e) {
      if (activeItem !== null) {
        activeItem.initialX = activeItem.currentX;
        activeItem.initialY = activeItem.currentY;
      }

      active = false;
      activeItem = null;
    }

    function drag(e) {
      if (active) {
        if (e.type === "touchmove") {
          e.preventDefault();

          activeItem.currentX = e.touches[0].clientX - activeItem.initialX;
          activeItem.currentY = e.touches[0].clientY - activeItem.initialY;
        } else {
          activeItem.currentX = e.clientX - activeItem.initialX;
          activeItem.currentY = e.clientY - activeItem.initialY;
        }

        activeItem.xOffset = activeItem.currentX;
        activeItem.yOffset = activeItem.currentY;

        setTranslate(activeItem.currentX, activeItem.currentY, activeItem);
      }
    }

    function setTranslate(xPos, yPos, el) {
      el.style.transform = "translate3d(" + xPos + "px, " + yPos + "px, 0)";
    }
  </script>
</body>
</html>

Thanks

You have a couple errors here.

  • querySelector uses a css selector syntax, so you’ll need to use .two or #two in it.
  • two can’t be cloned because it hasn’t been initialized yet.

I stripped it down a bit, and this does what I think you’re looking for. Styling and such it up to you but the basics are here. This assumes:

  1. The parent object is display flex, set to show the content in a row
  2. The item class is only on these objects - if you use it elsewhere, the query selector needs to be tweaked (something like “#container .item”

Thanks Dave ,
Very cool indeed , I added it to my “study this” Folder .

Sorry I didn’t explain my Post very well .

Here’s what the container will look like :

<div id="container">
<br>
<div class="item one" id="one">1</div>
<div class="item two" id="two">2</div>
<div class="item two" id="three">3</div>
<div class="item two" id="four" >4</div>
<div class="item two" id="five" >5</div>
<div class="item two" id="six">6</div>
<div class="item two" id="seven">7</div>
<div class="item two" id="nine">9</div>
</div>  <!--  id="container"  -->

When I click on 1 , a new 1 should be cloned .
When I click on 2 , a new 2 should be cloned …and so on .

You can get an idea of what I am up to here :
https://vmars.us/Guitar/GuitarScales-Beginning.html

Thanks

Ok Dave , I think I’m getting the hang of it : )

<!DOCTYPE html>
<html>
<title>
https://vmars.us/Guitar/myDrink-Clone.html 
</title>
<body>
<h1>The Element Object</h1>
<h2>The cloneNode() Method</h2>

<button onclick="cloneOne()">Coffee</button>
<br>
<button onclick="cloneTwo()">Tea</button>

<div class="item one" id="one"><p>Coffee</p></div>
<div class="item two" id="two"><p>Tea</p></div>

<script>
function cloneOne() {
  const node = document.getElementById("one").lastChild;
  const clone = node.cloneNode(true);
  document.getElementById("one").appendChild(clone);
}
</script>

<script>
function cloneTwo() {
  const node = document.getElementById("two").lastChild;
  const clone = node.cloneNode(true);
  document.getElementById("two").appendChild(clone);
}
</script>


</body>
</html>

You don’t need a separate method for each div as it will quickly become a maintenance nightmare - the version I gave you is still a better approach. I’ve tweaked it to create a copy and put it right after the clicked one. It does not change the inner text.

One unfortunate side effect here is this creates two object with the same id, which is a no-no, and will be noticeable if you change the text of the cloned object and try to clone that one. Because getElementById will pull the first instance of an id, it’ll always grab the first instance, which may not be what you want.

So you’re going to need to find a way to create unique ids which work for you if you want to clone and change objects.

Just having a play with this. The following could do with some refactoring

function clone(evt) {
  // evt.target is the clickedObject
  const clickedObject = evt.target;
  const parent = clickedObject.parentElement;
  const id = clickedObject.id;
  const existingClones = parent.querySelectorAll(`[data-clone-id='${id}']`);
  const numOfClones = existingClones.length
  const cloneId = `${id}_${String(numOfClones + 1).padStart(2, '0')}`
  
  const clonedObject = clickedObject.cloneNode(true);
  clonedObject.dataset.cloneId = id
  clonedObject.setAttribute('id', cloneId)
  clonedObject.addEventListener('click', clone);

  // if clones exist insert after the last clone
  const afterElement = (numOfClones > 0)
    ? existingClones[numOfClones - 1]
    : clickedObject
  // add cloned object directly after clicked object
  afterElement.after(clonedObject);
}

For each clone a dataset attribute is set with a reference to the source element e.g.

<div class="item" id="two">2</div>
<div class="item" id="two_01" data-clone-id="two">2</div>

In addition the id is set with a suffix which is created based on the number of clones of that source element.

Just an idea :slight_smile:

A bit of a tidy up

window.addEventListener('DOMContentLoaded', () => {

  const getCloneId = (id, num, pad = 2) => (`${id}_${String(num).padStart(pad, '0')}`)

  const getClone = (sourceElem, numOfClones = 0) => {
    const clonedObject = sourceElem.cloneNode(true);
    const id = sourceElem.id;

    clonedObject.dataset.cloneId = id;
    clonedObject.id = getCloneId(id, numOfClones + 1);
    clonedObject.addEventListener('click', clone);

    return clonedObject;
  }

  function clone(evt) {
    const sourceElem = evt.target;
    const parent = sourceElem.parentElement;
    const existingClones = parent.querySelectorAll(`[data-clone-id='${sourceElem.id}']`);
    const numOfClones = existingClones.length
    const clonedObject = getClone(sourceElem, numOfClones)

    // if clones exist insert after the last clone
    const elementToFollow = (numOfClones > 0)
      ? existingClones[numOfClones - 1]
      : sourceElem

    // add cloned object directly after clicked object
    elementToFollow.after(clonedObject);
  }

  document.querySelectorAll('.item')
    .forEach((item) => item.addEventListener('click', clone));  
})

Thanks Dave ,
I love the Vanilla JS .
I have changed three things :

  <div class="item" id="one" style="background-color: #F50000;" >1</div>
  item.addEventListener("dblclick", clone, true);
// clone it, add click event to it.
  let clonedObject = clickedObject.cloneNode(true);
  clonedObject.addEventListener("dblclick", clone); 

Ie., “dblclick” to clone , and single click to move balls around .

Thanks rpg_digital ,
Now I know how to setAttribute ,
and happy to know that a cloned element inherits all attributes ,
and can be changed .

clonedObject.setAttribute('id', cloneId)

xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
I am having issues with

  1. positioning : the round elements show as stacked , I want them inline .
  2. the Notes element is specified as draggable=“false” , but it is draggable .
  3. the biggest problem is that when a ball is cloned and moved into position ,
    and then a new ball is cloned , it repositions all the balls .
    I am thinking that I could pre-make 10 balls of each type
    then stack each type on top of themselves ;
    When the top ball is dragged off , there is another one waiting underneath .
    I know that that is low-tech , but it might work .

I don’t want to wear you folks out ,
so should I start a new Post for each issue ?
Here is where things stand so far .
https://vmars.us/Guitar/Guitar-onDBLclick-RED-add-Table-Need-Help.html
Thanks

Your code makes no distinction or exception for this attribute. It’s not using the HTML Drag and Drop API, so draggable has no weight here (unless you add something to give it effect).

Thanks m_hutley for your reply :
Still , it is draggable .
Why is it draggable
and how can I stop it from being draggable ??
https://vmars.us/Guitar/Guitar-onDBLclick-RED-add-Table-Need-Help.html

Thanks

Is this easier ?
https://jsfiddle.net/vmars316/79L5xn8s/5/

<div>s
Well I have made some headway .
Stacking <div>s , so far , is the only way I came up with
to separate <div>s movements from affecting other <div>s .
If anyone has a more high-tech way , I would sure like to know it .
Here’s what things look like now

https://vmars.us/Guitar/Guitar-7x2-Circles-DragDrop-1-Row-Table.html

I am still troubled that <p> is still Draggable .
So I will open up a new Post for that .
Thanks All :slight_smile:

This topic was automatically closed 91 days after the last reply. New replies are no longer allowed.