1

I have fetched some data from a URL and after that I have generated an HTML template for each item of received data using a forEach loop. After creating the HTML template for all the elements in response data I have assigned that to document.body.innerHTML. So it displays all elements with my HTML template.

The response has 30 objects and each object has a link (clone_url) with other information. I have generated HTML elements for those 30 objects using my HTML template, each has a name, profile Avatar and button to copy the specific link I have mentioned. I want to copy that link into the clipboard when user click the copy button.

Therefore, I want to add event listeners to the buttons. That is where I have been struggling because I have no idea how can I do that. Because I have to access each component and also I want to access related response object to get the link.

const url = "https://api.github.com/users/shrutikapoor08/repos";

fetch(url)
  .then((response) => response.json())
  .then((users) => {
    console.log(users);
    let htmlText = "";
    users.forEach((i) => {
      htmlText += `
        <div class="repo-container">
        <p class="repo-title">${i.name}</p>
        <div class="repo-owner">
          <img
            src="${i.owner.avatar_url}"
            alt="avatar"
            class="avatar"
          />
          <a href="${i.owner.html_url}" class="repo-owner-username">${i.owner.login}</a>
        </div>
        <div class="link-container">
          <button class="repo-link-btn">Copy</button>
          <p class="tooltip">Link Coppied</p>
        </div>
      </div>
        `;
    });
    document.body.innerHTML = htmlText;
  });
@import url("https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700;900&display=swap");

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

body {
  font-family: "Roboto", sans-serif;
}

.repo-container {
  background-color: rgb(255, 253, 251);
  border: 1px solid rgb(226, 226, 226);
  padding: 15px;
  width: 250px;
  margin: 10px;
}

.repo-title {
  font-weight: bold;
  margin-bottom: 10px;
}

.repo-owner {
  display: flex;
  align-items: center;
}

.avatar {
  width: 30px;
  height: 30px;
  border-radius: 50%;
  margin-right: 20px;
}

.repo-owner-username {
  font-size: 0.8rem;
  text-decoration: none;
}

.tooltip {
  background-color: rgba(0, 0, 0, 0.7);
  width: fit-content;
  padding: 4px 10px;
  border-radius: 10px;
  font-size: 0.75rem;
  color: white;
  opacity: 0;
  transition: transform 0.3s ease, opacity 0.3s ease;
  pointer-events: none;
}

.tooltip.active {
  opacity: 1;
  transform: translateY(-10px);
}

.link-container {
  display: flex;
}

.repo-link-btn {
  margin: 10px 0px;
}
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <link rel="stylesheet" href="./style.css" />
    <title>Document</title>
  </head>
  <body>
    <script src="./app.js"></script>
  </body>
</html>

This is the code I want to add with each button.

document.querySelector(".repo-link-btn").addEventListener("click", async () => {
  const text = "";
  await navigator.clipboard.writeText(text);
  document.querySelector(".tooltip").classList.add("active");
  setTimeout(() => {
    document.querySelector(".tooltip").classList.remove("active");
  }, 500);
});

const text should be equal to the response data objects URL. I have to access that data as well. What is the good practice for solving this problem?

Alireza Ahmadi
  • 8,579
  • 5
  • 15
  • 42
Kasun
  • 672
  • 8
  • 18
  • [Event delegation](https://dmitripavlutin.com/javascript-event-delegation/). Add one listener to a parent element rather than lots of listeners to lots of elements. The events bubble up the DOM to the parent listener which catches them. You can then determine what action to take based on the information in the event. – Andy Aug 08 '21 at 08:26
  • @Andy But finally how can I access fetched response data to get that specific link to each and every component? Because inside the event listener I wanna access related response data. – Kasun Aug 08 '21 at 11:11

1 Answers1

3

If you want to use pure JavaScript you need to create loop and add event listener to all item like this:

[...document.querySelectorAll(".repo-link-btn")].forEach(function (item) {..}

And to find clone_url value you can set custom attribute like data-cloneUrl="${i.clone_url}" and then get it in click time: e.target.getAttribute("data-cloneUrl");

Note that this code is based on your html and pure JavaScript. There is more easy way by jQuery.

        const url = "https://api.github.com/users/shrutikapoor08/repos";

        fetch(url)
            .then((response) => response.json())
            .then((users) => {
                console.log(users);
                let htmlText = "";
                users.forEach((i) => {
                    htmlText += `
        <div class="repo-container">
        <p class="repo-title">${i.name}</p>
        <div class="repo-owner">
          <img
            src="${i.owner.avatar_url}"
            alt="avatar"
            class="avatar"
          />
          <a href="${i.owner.html_url}" class="repo-owner-username">${i.owner.login}</a>
        </div>
        <div class="link-container">
          <button class="repo-link-btn" data-cloneUrl="${i.clone_url}">Copy</button>
          <p class="tooltip">Link Coppied</p>
        </div>
      </div>
        `;
                });
                document.body.innerHTML = htmlText;

                [...document.querySelectorAll(".repo-link-btn")].forEach(function (item) {
                    item.addEventListener("click", async (e) => {
                        const text = e.target.getAttribute("data-cloneUrl");
                        await navigator.clipboard.writeText(text);
                        document.querySelector(".tooltip").classList.add("active");
                        setTimeout(() => {
                            document.querySelector(".tooltip").classList.remove("active");
                        }, 500);
                    });
                })
            });
@import url("https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700;900&display=swap");

        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        body {
            font-family: "Roboto", sans-serif;
        }

        .repo-container {
            background-color: rgb(255, 253, 251);
            border: 1px solid rgb(226, 226, 226);
            padding: 15px;
            width: 250px;
            margin: 10px;
        }

        .repo-title {
            font-weight: bold;
            margin-bottom: 10px;
        }

        .repo-owner {
            display: flex;
            align-items: center;
        }

        .avatar {
            width: 30px;
            height: 30px;
            border-radius: 50%;
            margin-right: 20px;
        }

        .repo-owner-username {
            font-size: 0.8rem;
            text-decoration: none;
        }

        .tooltip {
            background-color: rgba(0, 0, 0, 0.7);
            width: fit-content;
            padding: 4px 10px;
            border-radius: 10px;
            font-size: 0.75rem;
            color: white;
            opacity: 0;
            transition: transform 0.3s ease, opacity 0.3s ease;
            pointer-events: none;
        }

            .tooltip.active {
                opacity: 1;
                transform: translateY(-10px);
            }

        .link-container {
            display: flex;
        }

        .repo-link-btn {
            margin: 10px 0px;
        }

Note that the snippet worked correctly But stack overflow snippet said The Clipboard API has been blocked because of a permissions policy applied to the current document. So check the snippet in your computer.

Alireza Ahmadi
  • 8,579
  • 5
  • 15
  • 42
  • 2
    @Kasun Has your problem been solved? let me know if there is a problem – Alireza Ahmadi Aug 08 '21 at 10:49
  • Yes this is working! But I don't understand how it works. I don't understand how it access response data object after `foreach` loop. `e.target.parentElement.previousElementSibling.getElementsByTagName('a')[0].getAttribute("href");` How this thing access that url. – Kasun Aug 08 '21 at 11:09
  • 1
    @Kasun Don't worry I explain for you. When you create an event the callback function parameter is event (say `e` here). and `e.target` is the target that you click on it. here is `button`. But your link is in the previous sibling of parent of button :) . I mean `div` with class `"repo-owner"` – Alireza Ahmadi Aug 08 '21 at 11:14
  • 1
    @Kasun So first I get the parent of button that is div with `link-container` class and then I try to access `previousElementSibling` which is div with `repo-owner` class. `e.target.parentElement.previousElementSibling` – Alireza Ahmadi Aug 08 '21 at 11:16
  • 1
    The final stage is get anchor tag from `repo-owner` class which is: `getElementsByTagName('a')` . I hope my explanation be clear for you – Alireza Ahmadi Aug 08 '21 at 11:18
  • you said, `The link link is in the previous sibling of parent of button :) . I mean div with class "repo-owner"`. This is the thing I can't understand. How it happens? – Kasun Aug 08 '21 at 11:19
  • @Kasun the link is anchor tag with `repo-owner-username` class. that is put in div with `repo-container` class OK? – Alireza Ahmadi Aug 08 '21 at 11:21
  • No its not the link. There is another link in response object called clone_link. That is what I wanna Access – Kasun Aug 08 '21 at 11:23
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/235747/discussion-between-alireza-ahmadi-and-kasun). – Alireza Ahmadi Aug 08 '21 at 11:24