1

I have a form input in my vue app that is used to create a password. I've successfully added a button to show/hide the password. I want to add a copy to clipboard function to let the user copy and paste the password in a safe place, but it's not working. What am I doing wrong here?

Template code

      <small class="font-wieght-bold text-success mb-0" v-if="isCopied">Copied</small>
      
      <div class="input-group">
        <input :type="showPassword ? 'text' : 'password'" class="form-control" ref="password" required v-model="password">
        <div class="input-group-append">
          <button class="btn btn-secondary" @click.prevent="copyToClipboard()"><i class="fas fa-clipboard"></i></button>
        </div>
      </div>

Vue code

    viewPassword() {
      this.showPassword = !this.showPassword;
    },
    copyToClipboard() {
      this.$refs.password.select();
      document.execCommand('copy');
      this.isCopied = true;
      setTimeout( () => { this.isCopied = !this.isCopied },3000);
    }
Pirate
  • 1,167
  • 1
  • 9
  • 19
newbiedev
  • 2,607
  • 3
  • 17
  • 65
  • 3
    does [this](https://stackoverflow.com/questions/54179004/how-to-copy-innerhtml-to-clipboard-in-vue-js/54179844) answer your question? It may not work when the `type=password`. – Pirate Dec 03 '20 at 15:52

2 Answers2

1

You need to copy the contents of your v-model, not the input itself.

You can use a function like this to copy from a variable.

It makes a new text box, uses the copy command, then immediately removes it. All in a single event loop so it never even renders.

const copyTextToClipboard = (text) => {
  const textArea = document.createElement('textarea')

  //
  // *** This styling is an extra step which is likely not required. ***
  //
  // Why is it here? To ensure:
  // 1. the element is able to have focus and selection.
  // 2. if element was to flash render it has minimal visual impact.
  // 3. less flakyness with selection and copying which **might** occur if
  //    the textarea element is not visible.
  //
  // The likelihood is the element won't even render, not even a flash,
  // so some of these are just precautions. However in IE the element
  // is visible whilst the popup box asking the user for permission for
  // the web page to copy to the clipboard.
  //

  // Place in top-left corner of screen regardless of scroll position.
  textArea.style.position = 'fixed'
  textArea.style.top = '0'
  textArea.style.left = '0'

  // Ensure it has a small width and height. Setting to 1px / 1em
  // doesn't work as this gives a negative w/h on some browsers.
  textArea.style.width = '2em'
  textArea.style.height = '2em'

  // We don't need padding, reducing the size if it does flash render.
  textArea.style.padding = 0

  // Clean up any borders.
  textArea.style.border = 'none'
  textArea.style.outline = 'none'
  textArea.style.boxShadow = 'none'

  // Avoid flash of white box if rendered for any reason.
  textArea.style.background = 'transparent'

  textArea.value = text

  document.body.appendChild(textArea)

  textArea.select()

  try {
    const successful = document.execCommand('copy')
    const msg = successful ? 'successful' : 'unsuccessful'
    console.log('Copying text command was ' + msg)
  } catch (err) {
    console.log('Oops, unable to copy')
  }

  document.body.removeChild(textArea)
}
Trevor
  • 2,792
  • 1
  • 30
  • 43
  • thank you for the answer. I will modify the method to directly copy the v-modeled data instead of selecting the input content and then copy it. Is there also a way to avoid creating the textarea to copy the data? – newbiedev Dec 03 '20 at 21:59
  • @newbiedev not that I know of. But it never appears. It all happens within a single event loop. – Trevor Dec 04 '20 at 19:23
0

As an alternative solution to the one of the accepted answer I've tried with the clipboard API. In particular I've used the clipboard.writeText() method to copy the v-modeled data directly into the clipboard and it worked also with <input type="password"> Here is the refactored code of copyToClipboard() method:

    copyToClipboard() {
      navigator.clipboard.writeText(this.password);
      this.isCopied = true;
      setTimeout( () => { this.isCopied = !this.isCopied },3000);
    }
newbiedev
  • 2,607
  • 3
  • 17
  • 65
  • 1
    writeText is a promise. You should .then it or await it. And it should error if the user rejects permission. So you should try/catch the await, or .catch the promise. – Trevor Dec 07 '20 at 17:01