Skip to content

Software Engineer at Polygon

Cloning DOM nodes and handling attached events

I’m the creator and sole maintainer of Siema — a simple carousel library that gained quite unexpected popularity on Github (thanks by the way). I constantly look to improve it and work hard to drop some new features in every once in a while. I recently came across a very minor challenge — I had to clone some DOM elements. Let me share with you some short and easy tips through what I learnt.

Cloning DOM elements #

To clone a DOM element we have two options: cloneNode() and importNode(). The differences between these two methods are very minor and it shouldn’t really matter which one you use to perform cloning within a single document. If you are a performance freak — jsPerf shows a slightly faster computation for importNode(). I doubt that you will ever need to duplicate thousands of elements on the page so I wouldn’t worry yourself about these numbers too much.

// using cloneNode()
const sourceElement = document.querySelector('.js-source div');
const destination = document.querySelector('.js-destination');

const copy = sourceElement.cloneNode(true);
destination.appendChild(copy);
// using importNode()
const sourceElement = document.querySelector('.js-source div');
const destination = document.querySelector('.js-destination');

const copy = document.importNode(sourceElement, true);
destination.appendChild(copy);

Reattach an event listener to a cloned element #

After cloning, the element loses reference to all events attached to it via JavaScript. It creates something commonly known as a shallow copy. We can manually reattach all event listeners to the cloned node but that sounds like a tedious task. Back in the day we could find something like EventListenerList() in the DOM spec. That would be very helpful in solving our issue but unfortunately it has been removed from the specification and the implementation isn’t available on any browser. The reason for ditching this part of the spec can be found on multiple W3C mailing conversations.

[…] what is the motivation for adding this functionality at all? Previously, the working group resolved to remove the related but less powerful hasEventListenerNS method for lack of a use case, and because there are potential security issues.

Don’t be tricked by getEventListeners() either as this is only a part of the Chrome Command Line API and is available only from the Google browser’s console. You cannot use it in your scripts.

Because the native method for checking the events attached to an element doesn’t exist we need to find a different solution. Let’s have a look at the available options.

Inline events #

A little bit old-school but it will do in some circumstances. HTML elements allow us to add an event attribute with a tiny bit of JavaScript functionality inside it. Like so:

<div onclick="alert('Hello')">some element</div>
<div onclick="clickHandler()">some element</div>

Although it is not very elegant, not pleasant to maintain and can cause some accessibility difficulties sometimes it can be the best way to go. As always — it depends on the situation…

Event delegation #

Instead of adding an event listener to every element, let’s just add it once to a parent element and take advantage of event bubbling. Sounds complicated but it is easier than you think and can save you from potential memory leaks and performance degradation. Here’s an example:

<ul class="list">
  <li class="list__item">Ed</li>
  <li class="list__item">Edd</li>
  <li class="list__item">Eddy</li>
</ul>

Adding a listener to every list item…

// declare handler
function clickHandler(e) {
  console.log(e.target.innerHTML);
}

// reference to all list items
const items = document.querySelectorAll('.list__item');

// loop through list items and add listener to click event
for (const item of items) {
  item.addEventListener('click', clickHandler);
}

Instead, it’s better to do this…

// declare handler
function clickHandler(e) {
  if (e.target.matches('.list__item')) {
    console.log(e.target.innerHTML);
  }
}

// reference to a list
const list = document.querySelector('.list');

// add a single listener on list item
list.addEventListener('click', clickHandler);

Do you already know where I’m going with this in the context of cloned elements? Instead of fighting with attaching handlers to cloned nodes, attach a single event on the closest common parent element. Makes sense?

jQuery clone() method #

Popular DOM libraries like jQuery, YUI and Moo have their own methods for event delegation and I highly recommend using them if you can. The most popular one — jQuery — uses wrapper methods to deal with events. It internally tracks all the handlers attached to the node so whenever we use the clone() method it creates a deep copy (optional argument) that contains the source element’s events.

$('.js-source div').clone(true).appendTo('.js-destination')

Example #

Hopefully that made sense and this article helped you out. Thanks for reading and don’t forget about the share buttons below this article — I’m sure that your friends don’t know much about cloning yet. I put together two examples for you to play with. The first one uses event delegation and cloneNode(), and the second one uses the jQuery clone() method. Peace!

See the Pen 2017.06.27 - clone elements (vanilla js) by Pawel Grzybek (@pawelgrzybek) on CodePen.

See the Pen 2017.06.27 - clone elements (jQuery) by Pawel Grzybek (@pawelgrzybek) on CodePen.

Comments

  • P
    Piotr z blog.piotrnalepa.pl

    In general, the article is good, but referencing YUI and Moo as popular DOM libraries seems a bit weird. It makes me thinking that the article was previously written somewhere else.

    👆 you can use Markdown here

    Your comment is awaiting moderation. Thanks!
    • Pawel Grzybek
      Pawel Grzybek

      It was definitely not. I wrote it yesterday and you can find every single stage of this post on Github history as my website is 100% open source :)

      https://github.com/pawelgrz...

      I need to confirm that I don't have an experience with YUI and Moo. I found a reference to this information during my research / preparation. I have a solid understanding of jQuery tho so I assumed that the remaining listed libs have some similar system of events management. Any clarification needed?

      👆 you can use Markdown here

      Your comment is awaiting moderation. Thanks!
      • P
        Piotr z blog.piotrnalepa.pl

        Then everything's fine. I would just remove references to these old, obsolete libraries.

        👆 you can use Markdown here

        Your comment is awaiting moderation. Thanks!
  • E
    EML

    Thanks. Very useful.

    👆 you can use Markdown here

    Your comment is awaiting moderation. Thanks!
  • b
    bheku

    much appreciated thanks!

    been developing cor years and never thought of this approach. can be used in a lot of different contexts

    👆 you can use Markdown here

    Your comment is awaiting moderation. Thanks!
  • A
    Adam

    Nice article. +1 for that Ed, Edd, and Eddy reference - that show was great!

    👆 you can use Markdown here

    Your comment is awaiting moderation. Thanks!
    • Pawel Grzybek
      Pawel Grzybek

      Yeah , I absolutely love Ed, Edd, and Eddy!

      👆 you can use Markdown here

      Your comment is awaiting moderation. Thanks!
  • J
    JM

    Super explanation with examples. Thank you.

    👆 you can use Markdown here

    Your comment is awaiting moderation. Thanks!
    • Pawel Grzybek
      Pawel Grzybek

      I am glad it helped you out!

      👆 you can use Markdown here

      Your comment is awaiting moderation. Thanks!
  • C
    Chris M

    Hey Pawel, I appreciate this article! I'm new to JavaScript but making headway.

    Quick question, when declaring the handler what the the e represent?

    👆 you can use Markdown here

    Your comment is awaiting moderation. Thanks!
    • Pawel Grzybek
      Pawel Grzybek

      In the example above, the e argument, is just a lazy way of saying event. This is what callback function will be implicitly invoke with. Hopefully that helps.

      👆 you can use Markdown here

      Your comment is awaiting moderation. Thanks!

Leave a comment

👆 you can use Markdown here

Your comment is awaiting moderation. Thanks!