I'm making a course on Laravel Sanctum: MasteringAuth.com

Building a CopyToClipboard renderless component in VueJS

Constantin Druc ยท 31 May, 2021

[00:01] The main difference between a regular component and a renderless component is that the latter doesn't output any html on its own. Its sole purpose is to expose the html is provided by whoever uses the component; and that is super helpful when you need two visually different versions of the same component.

[00:22] In my case I want a copy to clipboard button that replaces the copy text with the confirmation text and another version of the same thing that shows the confirmation text next to it.

[00:33] So these two share the same functionality but they are visually different. That's what renderless components allows us to do; have many visually different versions of the same component.

[00:44] So let's get started. I'm going to create a new view component called CopyToClipboard, and since we won't have any html we'll completely skip the template tag and jump straight to the script tag.

[01:02] When the user clicks the copy button we'll need to toggle a status attribute to display the confirmation text so let's add that inside our data.

[01:14] Then, inside the methods, we need a copy method that will copy to clipboard whatever text we pass to it. Now the most common way to do this is to create a textarea element, assign it the value of the text we want to copy, style the text area in such a way that it won't be visible on the screen, select its contents, and execute the copy command on it, and then finally, remove it from the document.

[01:58] To create the text area we can do const, el from element, document dot create element text area, and then to assign the value we can do: el.value equals text. And now to style the text area in a way that it won't be visible on the screen we can set the position to absolute and then give it a really large negative number for the left property.

[02:24] So we can do el style dot position equals absolute, and then for the left we can say something like negative 90000 pixels and then finally, of course, we need to append element to the body; so document body append child element.

[02:44] Now that we have the textarea in place, we can select its contents using el.select(), execute the copy command to grab the value, and then finally, since we copied the value, we no longer need the text area so let's remove the element from the page.

[03:09] To offer some feedback that the text has been copied, we need to toggle the status for a brief second; otherwise people clicking the button will have no idea if the text was copied or not. So let's set the status to copied and then use setTimeout to set it back to idle after a second has passed.

[03:36] The only thing remaining is to expose the copy method and the status attribute so they can be used by the parent component; and we can do that inside the render function. We'll need to return this.slots.default, which is the default slot and here we can pass an object with whatever we want to expose. In our case we need to expose the satus attribute and the copy method. And that's it; our component is ready to be used.

[04:12] I'll go to the media library file and the first thing we'll need to do is to import the component; so import CopyToClipboard, and then register it.

[04:25] And now we can start using it we'll do: CopyToClipboard, and then v-slot to access the object we passed in; this will contain our status attribute and the copy method. And then move the button inside our component to start using the exposed functionality.

[04:48] We'll add the click event handler that will call the copy method with the text we want to copy. In our case it's media.url, and then use the status to disable the button; so props.status === "copied". This should disable the button, and then use the same status to display the confirmation text. So if props.status === "copied", then display "Copied", otherwise "Copy".

[05:23] Now let's go back in the browser and, refresh, click the copy button, and here it is. Let's paste the contents and, of course, it works.

[05:35] Let's also style the button a bit. We'll do class, and check if the status is copied, and if it's so we'll make the text green, otherwise we'll copy this part and make it blue.

[05:59] Let's go back in the browser, refresh, click the copy button, and here it is.

[06:03] Let's move on to the second type of button. The one that shows the confirmation text next to it. I'll go to EditMedia and the first thing I'll do is to import the CopyToClipboard component, and then register it, and then use it in the same way.

[06:25] We'll do CopyToClipboard and then v-slot to access the object we passed in. Copy the button and place it inside our component, and start using our exposed functionality. So on click I'll do props.copy and pass in the text I want to copy, which will be media.url. Then I'll set the button to disabled when props.status === 'copied', and then finally, I will display the confirmation message: I'll do v-if="props.status === 'copied'". And to style it a bit, we'll do text-sm text-green-600.

[07:17] Now, to make sure these are aligned, I'll wrap them inside a flex, flex items-center space-x-2, and that's it. Let's go in the browser, refresh, click the button, and here is our text.

[07:41] Let's go over the component one more time. We use a status data property to toggle the confirmatino message, we add a copy method that creates a textarea element, sets the value to the text we want to copy, then styles the element in such a way that it won't be visible on the screen. We then select and copy the element's value, and then we remove it from the body. Finally, we toggle the status for a bit, just so the user sees that the text has been copied. Once everything is in place, we expose the functionality inside the render function.

[08:23] And that's it. That's how you can build a renderless CopyToClipboard component using vue.js.