/ React

Let your users share it!

Last week I made a thing. It's a simple thing, a group photo booth, which I made to show students the power of real time data streams using sockets and RethinkDB's change feeds. You can check out the code if you like.

After a short talk about the difference between pull and push data flows I was demoing by having everyone join a room for a group photo. In an humorous moment I realized that intuitively I expected my application to have a button to get a shareable link to the groups booth, only I hadn't made one! I chuckled and continued the presentation, copying the address from the address bar, but I went back the next day to see about adding that button. I learned that browsers are actually fairly strict about accessing the systems clipboard, this is how I made it happen.

Restrictions

There are two specific rules that are very impactful on your final method:

  • You can only save data from a DOM node
    • This means you cannot just use random string data(app state)
    • This node cannot be hidden
  • You can only access the clipboard in response to a click event
    • You cannot automatically add to the clipboard as a result of some other data flow

In this case I had no interest in programmatically deciding to put some DOM node on the clipboard, I was building a button, but it sure would have been easy to just grab the url I wanted to share out of Window.location or some saved state! Alas, it was not to be. So what to do?

Solution

Well I knew I had to use a visible DOM node so:

  <p id="roomLink">{ location.href.split('?')[0] }</p>

But I obviously don't want this to be sitting in my UI!

  <p id="roomLink"
      style={{ position: 'absolute', left: '-999em' }}
    >{ location.href.split('?')[0] }</p>

Ahhh, much better! Just move it off the page! But, wait, it has to be on a click event right? Yup! Good news! You don't have to copy what you click, you just have to click! In that event handler event handler you can make use of a nifty interface called a Range.

Long and short is that a Range stores the data from a selection of the current page. There is a nice method on the page which allows you to create add pieces to the interface: document.createRange().

getLink() {
  const link = document.getElementById('roomLink');
  const range = document.createRange();
  range.selectNodeContents(link);
  const selection = window.getSelection();
  selection.removeAllRanges();
  selection.addRange(range);
  document.execCommand('copy');
}

Once you have a range interface selectNodeContents(<node>) allows you to pass data into the range. Here I passed the <p> which I stored the url in. In my case I wanted to clear any existing selection that user had so that they would only get the clean link. window.getSelection() gives me the current selection object and calling removeAllRanges() on this selection clears it. Then I pass in my new range with my url in it to the clean selection. Finally I call document.execCommand() passing in the 'copy' command.

document.execCommand() allows you to execute a variety of common key shortcut commands like copy, cut and paste. It also includes an array of formatting commands like indent, font size and bold. Definitely check it out if you haven't.

And that's all it takes! While I would love to have something like

window.clipBoard.copy(data)

the actual solution was rather painless and achieved my goals very well.