Tutorials

How To Make A Copy URL Button In Rails

How to make a copy URL button in Rails using Stimulus JS

4 minute read
How To Make A Copy URL Button In Rails

How to make a copy URL button in Rails using Stimulus JS

  • note: This already assumes you have a basic blog built in Rails, and I am using bootstrap in this example.

Do you have a blog built with Rails and want to make it easy for other people to copy the link to a blog post and share it with the world? This is the tutorial for you!

The first thing we need to do is make our Stimulus controller. So in the app/javascript directory make a clipboard_controller.js file. In that file put in this code for now

import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  connect() {
    console.log("Hello from the clipboard controller!")
  }
}

Next, we need to wire up our controller in our view. If you made your blog by scaffolding, you will probably want this to be in the blog erb partial(views/blogs/_blog.html.erb).

<h2>Blog Post:</h2>
<p>
  <strong>Title:</strong>
  <%= blog.title %>
</p>
<p>
  <strong>Content:</strong>
  <%= blog.content %>
</p>
<p>
  <strong>Posted By:</strong>
  <%= blog.user.email %>
</p>
<% if blog.image.attached? %>
  <div>
    <%= image_tag blog.image.variant(:medium), class: "mb-2" %>
  </div>
<% end %>

<div data-controller="clipboard">
  <button class="btn btn-primary">Copy URL</button>
</div>


The key takeaway here is the <div> with the data-controller="clipboard", this is how we actually use our Stimulus controller that was created. Now start your dev server and go to a blog post, open up your browser dev tools, and in the console you should see "Hello from the clipboard controller!". Congrats! You have successfully wired up your stimulus controller to your view! Now we can do cool stuff!

How can we copy the URL??

So in order implement the functionality that we want, we need a few things. We need to get our URL string into our Stimulus controller, we need to copy that string to the users clipboard, and we need to somehow notify the user that it was copied to their clipboard when clicking the button. So how do we get our URL string into our controller? By using Stimulus targets and a hidden input, and a Stimulus data action on the button. Here's what it looks like in code on the HTML side

<div data-controller="clipboard">
  <input type="hidden" value="<%= blog_url(blog) %>" data-clipboard-target="source">
  <button id="copyBtn" data-action="clipboard#copy">Copy URL</button>
</div>


and our controller

import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  copy() {
    console.log("I have been clicked!")
  }
}

So if you refresh your blog page and click the button, in dev tools console you should see "I have been clicked!", now we're getting somewhere! So we added our hidden input holding the URL of the blog, as well as our button with an added ID and data-action. So the data action will always be the name of the controller, and the function you want inside that controller. It should be noted that "clipboard#copy" is a Stimulus even shorthand, and you can also do "click->clipboard#copy" if you want to be explicit. However for some common element/event pairs, like a button/click, you don't need the "click->" preface.

So what's up with the <input type="hidden" value="<%= blog_url(blog) %>" data-clipboard-target="source"> ?
Here is where the magic happens. So in the controller we can grab that blog_url with the target. Looks like this

import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  static targets = ["source"]

  copy() {
    console.log(this.sourceTarget.value)
  }
}

So now if you refresh your page, in your browser dev tools console, when you click your button, you should see the full URL of your page in the console!(something like localhost:3000/blogs/1). How cool is that??!! Ok so now comes in some good old classic JavaScript. We can now copy that URL to our clipboard. Add this line to your copy() function and give it a tr

    navigator.clipboard.writeText(this.sourceTarget.value)

After clicking the button, you should be able to paste your URL! Now as a user, when you click the Copy URL, how do you know that the link was copied? You don't unless you try pasting. Not the greatest UI. An easy solution is to change the button's text saying it was copied, and change it back to Copy URL. Remember that ID we gave the button? This is why, so we can target the button and change the text when it's clicked. Let's see it in action.

import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  static targets = ["source"]

  copy() {
    const copyBtn = document.getElementById("copyBtn")

    navigator.clipboard.writeText(this.sourceTarget.value)
    copyBtn.innerText = "Copied!"

    setTimeout(() => {
      copyBtn.innerText = "Copy URL"
    }, 2000)
  }
}

So now when you click the button, it should say Copied!, and after 2 seconds(it's in milliseconds so 2000 = 2 seconds, 3000 = 3, and so on), it will return to Copy URL. Awesome!! You can play around with the timeout timer and set it to whatever you like(longer than 2 seconds, shorter, do what you want!) Not too shabby! Well that will conclude this tutorial, hope you learned something! If you want to learn more, or more in depth about how actions, controllers, targets, etc work, you can check out the official Stimulus documentation https://stimulus.hotwired.dev
You can also learn more about Hotwire and Stimulus here https://hotwire.io/documentation