After making lots of phoenix liveview apps I’ve found myself re-using the same code snippets all the time. I thought I’d start compiling these snippets so it’s easier for myself and others to find them. Here’s the first: copying text to the clipboard.
It’s a bit tricky to get this right. My requirements are:
- Works across browsers
- The copied content keeps paragraph breaks in initial text
- The copy button changes from “Copy” to “Copied” after a moment or two
- I can re-use the copy button easily
- I can have multiple copy buttons on the same page and that’s not annoying
I found solutions Fly’s blog and the Phoenix docs, but they don’t satisfy requirements 3 4 or 5. Here’s here I do it.
The Code
Here’s the code for the copy_button. You can paste this directly into your core_components.ex
file.
Note that the content
attribute is the id of the element you want to copy. When this button is clicked,
it sends a JS.dispatch
event to the app.js
file.
attr :id, :string, required: true
attr :content, :string, required: true
@doc """
Copy to clipboard button.
"""
def copy_button(assigns) do
~H"""
<button
id={@id}
content={@content}
phx-click={JS.dispatch("phx:copy", to: "##{@content}")}
type="button"
class="rounded-md inline-flex items-center bg-white px-2.5 py-1.5 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50"
>
Copy
</button>
"""
end
Next, we need to add the phx:copy
event handler to our app.js
file. It took some digging, but
I discovered that the event dispatch sends the button element as the detail.dispatcher
. This means we can set the button text without needing to pass in the button id.
window.addEventListener("phx:copy", (event) => {
let button = event.detail.dispatcher;
let text = event.target.innerText;
navigator.clipboard.writeText(text).then(() => {
button.innerText = "Copied!";
setTimeout(() => {
button.innerText = "Copy";
}, 2000);
});
});
Now, use it
Finally, we can use the component in our liveview template. All it needs is an id
and the id of the element we want to copy (content
).
<div>
<p id="stuff">Stuff I want to copy</p>
<.copy_button id="copy-button" content="stuff" />
</div>
We can have multiple copy buttons on the same page and it won’t be an issue.
PS
I realize that this blog itself doesn’t have a copy button! That’s because it’s written in Next.js and I’m still figuring that out 😃.