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
eventwindow property is not advisable, particularly so in anasyncfunction 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
eventas 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.addEventListenersyntax.
- 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-containerdivision. While you could useclosestto find the division, it's already available asevent.currentTargetbecause that is where the event is being processed.Element (getter) properties
nextSibling,previousSibing,firstChildandlastChildare quite capable of returning text and HTML comment nodes - which is not what you want in this case. UsenextElementSibling,previousElementSibling,firstElementChildandlastElementChildversions 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.
