Home > database >  Why can't I call the correct div with parentNode and nextSibling?
Why can't I call the correct div with parentNode and nextSibling?

Time:01-21

This is my code:

const copyContent = async() => {
  try {
    let copyText = event.target.parentNode.lastChild.value;
    let copied = event.target.nextSibling.nextSibling;

    await navigator.clipboard.writeText(copyText);
    console.log('Content copied to clipboard');
    copied.style.animation = "appear 4s linear";
  } catch (err) {
    console.error('Failed to copy: ', err);
  }
}
<div >
  <div >
    <div  onclick="copyContent()">
      <div>
        <img src="https://google.com">
        <span>#1</span>
      </div>
      <div >
        <span>Copied!</span>
      </div>
    </div>
    <input  type="text" value="https://google.com">
  </div>


  <div >
    <div  onclick="copyContent()">
      <div>
        <img src="https://yahoo.com">
        <span>#2</span>
      </div>
      <div >
        <span>Copied!</span>
      </div>
    </div>
    <input  type="text" value="https://yahoo.com">
  </div>
</div>

This is the error I get:

Failed to copy: TypeError: Cannot read properties of null (reading 'style') at copyContent

I know where I'm going wrong is here:

let copyText = event.target.parentNode.lastChild.value;

let copied = event.target.nextSibling.nextSibling;

But I've tried many different combinations and it doesn't work. I'm avoiding using an id because I'm going to have MANY "cards".

CodePudding user response:

Issues that needed addressing:

  1. Use of the global event window property is not advisable, particularly so in an async function that potentially could read an Event Object for an event that occurred after it was called.

    • If the event handler is added in an HTML onEventName attribute, pass event as an argument to the function called. (If you are wondering, 'event' is passed as an argument to a function generated by the HTML parser from the attribute's text value.)
    • Better would be to add the event handler in JavaScript using the element.addEventListener syntax.
  2. Click event targets are the element that was under the mouse when clicked. In this case it could be one of sundry child elements at different levels of nesting within the .card-container division. While you could use closest to find the division, it's already available as event.currentTarget because that is where the event is being processed.

  3. Element (getter) properties nextSibling, previousSibing, firstChild and lastChild are quite capable of returning text and HTML comment nodes - which is not what you want in this case. Use nextElementSibling, previousElementSibling, firstElementChild and lastElementChild versions instead when looking for elements.

Here's a reworked example, using a transition rather than an animation because it wasn't included in the post. The value copied to the clip board is the text content of the input element following the card container - amend if something else if supposed to be copied.

"use strict";
const copyContent = async(event) => {  // takes event object argument
  try {
    let currentTarget = event.currentTarget; // should be div.card-container 
    const input = currentTarget.parentNode.lastElementChild;
    const copyText =input.value;

      console.log("currentTarget = %s.%s", currentTarget.tagName, currentTarget.className); // debug
      console.log("input: ", input)
      console.log( "copyText:", copyText);
    let copied = currentTarget.firstElementChild.nextElementSibling;
    await navigator.clipboard.writeText(copyText);
      console.log('Content copied to clipboard');
    copied.style.color = "forestgreen"; // simplified
  } catch (err) {
    console.error('Failed to copy: ', err);
  }
}
.card-container {
  border: thin solid green;
  background-color: honeydew;
}
.copied { 
  color: white;
  transition: color 3s;
}
<div >
    <div >
        <div  onclick="copyContent(event)">
            <div>
                <img src="http://example.com">
                <span>#1</span>
            </div>
           <div >
               <span>copied to clipboard</span>
           </div>
        </div>
       <input  type="text" value="http://example.com">
  </div>


  <div >
    <div  onclick="copyContent(event)">
      <div>
        <img src="https://yahoo.com">
        <span>#2</span>
      </div>
      <div >
        <span>copied to clipboard</span>
      </div>
    </div>
    <input  type="text" value="http://example.com">
  </div>
</div>

CodePudding user response:

First, you are not passing the event on the onclick, it should be onclick="copyContent(event)"

Second, as some comments have mentioned, event.target maybe not the element you think. The onclick is attached to card-container div and all its child, so if you click the span with the text Copied!, the image or other thing the target will change.

A solution for this can be use closest method

const copyContent = async(event) => {
  try {
    //if parent node its the actual target, use actual target as parent node, instead search for the closest element that has card-container class
    let parentNode = event.target.classList.contains("card-container") ? event.target : event.target.closest(".card-container")
    //from the parent node, search for the input
    let copyText = parentNode.querySelector("input.link");
    //from the parent node, search the copied span element?
    let copied = parentNode.querySelector(".copied span)";

    await navigator.clipboard.writeText(copyText);
    console.log('Content copied to clipboard');
    copied.style.animation = "appear 4s linear";
  } catch (err) {
    console.error('Failed to copy: ', err);
  }
}

Observation: I think making the function async should be unnecesary as also awaiting navigator

CodePudding user response:

The error message you're getting indicates that event.target is null, which means that the event object is not being passed to the copyContent function. One way to fix this is by passing the event object as an argument to the function.

<div  onclick="copyContent(event)">

Then in the function, you can reference the event passed as a parameter const copyContent = async(event) => {... . Also you may consider using event.currentTarget instead of event.target in your function since the difference: target is the element that triggered the event (e.g., the user clicked on), and currentTarget is the element that the event listener is attached to. Another idea is to use querySelector to select the element as well.

let copyText = event.currentTarget.parentNode.querySelector('.link').value;
let copied = event.currentTarget.querySelector('.copied'); //Not sure if this is what you want

You can use this querySelector method to select the element, so you don't have to use parentNode and nextSibling.

  • Related