Implement Click-And-Copy With Clipboard API
Tags: webdev, javascript, webapi, tutorial
Published: 2023-03-06 10:00:00

Click-and-copy is everywhere these days. By simplifying a multi-step action to a single-step one, this functionality makes web users happy so as a UI/UX designer, you don't want to miss out on it.

At the core of the implementation is the [Clipboard API](https://developer.mozilla.org/en-US/docs/Web/API/Clipboard). It may sound daunting at first, but the logic is actually quite straightforward. I'll help you get familiar with it by building a real world example together in this tutorial.

---

## Table of Contents

- [What We Will Build](#what-we-will-build)
- [HTML & CSS](#html---css)
- [Clipboard API](#clipboard-api)
  * [An Overview](#an-overview)
  * [Elements and Event Listeners](#elements-and-event-listeners)
  * [Click-and-Copy Handler](#click-and-copy-handler)
  * [Form Submission Handler](#form-submission-handler)
- [Conclusion](#conclusion)

---

## What We Will Build

Our example will look and work like this when finished:

![Click-and-copy example](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/84qje46b4aouqk3689z9.gif)

<figcaption>Click-and-copy example</figcaption>

When you click a coupon code, it will be selected and highlighted, and a tooltip with the copied coupon code will appear. You can then paste the content to the coupon code input to apply it, which mimics how we work with coupon code in the real world.

For brevity, I will only show the code that's relevant to the step in the description below. You can find the full code of the example [here](https://github.com/jw1219/clipboard-api-example).

---

## HTML & CSS

Let's start by creating a `index.html` and `style.css` file at our project root.

Give `index.html` the following content (full code can be found [here](https://github.com/jw1219/clipboard-api-example/blob/main/index.html))

```html
<div class="container">
  <div class="coupons">
    <div class="coupon">
      <small class="coupon-label">Coupon Code: </small>
      <input type="text" name="coupon-code-1" id="coupon-code-1" size="10" value="ABC12345" readonly>
      <small class="tooltip"><span class="copied-code"></span> Copied</small>
    </div>
    <div class="coupon">
      <small class="coupon-label">Coupon Code: </small>
      <input type="text" name="coupon-code-2" id="coupon-code-2" size="10" value="XYZ098765" readonly>
      <small class="tooltip"><span class="copied-code"></span> Copied</small>
    </div>
  </div>
  <form autocomplete="off">
    <label for="code-input">Your Coupon Code: </label>
    <div>
      <input type="text" name="code-input" id="code-input">
      <button type="submit">Apply</button>
    </div>
  </form>
</div>
```

Then give `style.css` the following content (full code can be found [here](https://github.com/jw1219/clipboard-api-example/blob/main/style.css))

```css
body {
  height: 50vh;
  background-color: beige;
  display: flex;
  justify-content: center;
  align-items: center;
}
.container {
  display: flex;
  flex-direction: column;
  align-items: stretch;
}
.coupons {
  margin-bottom: 20px;
}
@media (min-width: 768px) {
  .coupons {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    column-gap: 2%;
  }
}
.coupon {
  background-color: white;
  border: 2px solid black;
  display: flex;
  justify-content: space-between;
  align-items: center;
  position: relative;
  padding: 10px;
  margin-bottom: 20px;
}
.coupon:last-child {
  margin-bottom: 0;
}
@media (min-width: 768px) {
  .coupon {
    margin-bottom: 0;
  }
}
.coupon-label {
  white-space: nowrap;
  margin-right: 5px;
}
input[name^='coupon-code-'] {
  border: none;
  outline: none;
  font-size: large;
  font-weight: bold;
  text-align: center;
}
.tooltip {
  background-color:darkslategray;
  color: white;
  padding: 5px 10px;
  border-radius: 5px;
  white-space: nowrap;
  position: absolute;
  left: 50%;
  bottom: 100%;
  transform: translate(-50%, -6px);
  display: none;
}
.tooltip::after {
  content: '';
  position: absolute;
  top: 100%;
  left: 50%;
  border-top: 5px solid darkslategray;
  border-bottom: 5px solid transparent;
  border-left: 5px solid transparent;
  border-right: 5px solid transparent;
}
form {
  width: 100%;
}
form div {
  width: 100%;
  position: relative;
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  column-gap: 2%;
}
label[for='code-input'] {
  display: block;
  margin-bottom: 10px;
}
input[name='code-input'] {
  grid-column: span 3 / span 3;
  padding: 10px;
  border: 1px solid black;
}
button[type='submit'] {
  grid-column: span 1 / span 1;
  border: 2px solid black;
  background-color: transparent;
  padding: 10px 20px;
}
```

---

## Clipboard API

### An Overview

Before implementing the API, let's take a quick look at what we can do with it.

This API allows us to access the system clipboard through the following methods:

- `read()`. Get the data (not limited to text) from the clipboard.
- `readText()`. Get the text data from the clipboard.
- `write()`. Write data (not limited to text) to the clipboard.
- `writeText()`. Write text data to the clipboard.
Now let's implement the Clipboard API in Javascript to give our example the expected behavior.

All the methods return a [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise). For the `read*` methods, the promise is resolved with the data of the clipboard and for the `write*` methods, the promise is resolved when the data is copied to the clipboard.

Note that,

- We access the `Clipboard` object through the `navigator.clipboard` global.
- The support of the API varies across browsers. Check out [browser compatibility](https://developer.mozilla.org/en-US/docs/Web/API/Clipboard#browser_compatibility) to see if your browser supports it.
- Permission is required for the API to access the system clipboard, so an alert may appear that asks for your permission when calling the API for the first time (if that's the case, click _allow_).

### Elements and Event Listeners

Now let's get started coding! First, create a `script.js` file at the project root. Then, let's select and store the elements we'll use in variables and prepare our event listeners:

```javascript
const codeEls = document.querySelectorAll('input[name^="coupon-code-"]')
const tooltips = document.querySelectorAll('.tooltip')
const form = document.querySelector('form')
const codeInput = document.querySelector('input[name=code-input]')

/**
 * todo: define handleCopy and handleSubmit methods
 */

for (const codeEl of codeEls) {
  codeEl.addEventListener('click', handleCopy(codeEl))
}

form.addEventListener('submit', handleSubmit)
```

### Click-and-Copy Handler

The `handleCopy()` method contains the core business logic for the click-and-copy action. In our example, we only deal with text data, so we use `readText()` and `writeText()` here.

```javascript
let timeout

const handleCopy = el => {
  return () => {
    /**
     * 1. copy coupon code to clipboard
     */
    navigator.clipboard.writeText(el.value)
      .then(() => {
        /**
         * 2. select and highlight clicked coupon code
         */
        el.setSelectionRange(0, -1)
        /**
         * 3. hide all currently displayed tooltips
         *    if there is any
         */
        tooltips.forEach(tooltip => tooltip.style.display = 'none')
        /**
         * 4. select the tooltip for the clicked coupon
         */
        const tooltip = el.nextElementSibling
        /**
         * 5. insert the copied code to the tooltip
         *    before displaying the tooltip
         */
        const copiedCodeEl = tooltip.querySelector('.copied-code')
        navigator.clipboard.readText()
          .then(code => copiedCodeEl.textContent = code)
        tooltip.style.display = 'block'
        /**
         * 6. clear any existing timeout
         *    before assigning a new one to the variable
         */
        if (typeof timeout === 'number') {
          clearTimeout(timeout)
        }
        /**
         * 7. display the tooltip for 2 seconds
         */
        timeout = setTimeout(() => {
          tooltip.style.display = 'none'
          copiedCodeEl.textContent = ''
        }, 2000)
      })
  }
}
```

### Form Submission Handler

In `handleSubmit`, we'll just get the input value of the text field and display it in an alert:

```javascript
const handleSubmit = (event) => {
  event.preventDefault()
  const formData = new FormData(form)
  const code = formData.get('code-input')
  /**
   * clearing the text field after form submission
   * makes great UX
   */
  formData.set('code-input', '')
  codeInput.value = ''
  alert(`Coupon Code ${code} has been applied successfully.`)
}
```

And that's it. Make sure to get your hands dirty and write the code line by line to really understand the concept.

The full Javascript code can be found [here](https://github.com/jw1219/clipboard-api-example/blob/main/script.js).

And the final and live example can be found [here](https://jw1219.github.io/clipboard-api-example/).

---

## Conclusion

In this tutoria, we learned how to implement a _click-and-copy_ component by building an example that can be used out of the box in your next project.

We looked at the `Clipboard API` which is the core to our implementation. The Javascript code looks somewhat lengthy but the idea is not that complex, especially when we break the steps down.