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:
Use of the global
event
window property is not advisable, particularly so in anasync
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.
- If the event handler is added in an HTML onEventName attribute, pass
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 useclosest
to find the division, it's already available asevent.currentTarget
because that is where the event is being processed.Element (getter) properties
nextSibling
,previousSibing
,firstChild
andlastChild
are quite capable of returning text and HTML comment nodes - which is not what you want in this case. UsenextElementSibling
,previousElementSibling
,firstElementChild
andlastElementChild
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
.