Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
Help us understand the problem. What is going on with this article?

vue-clipboard2をVuetifyのv-dialogの中で動かす

システムから発行したIDやパスワードなどを、ユーザーが画面上で簡単にクリップボードにコピーできるようにしたいなと思って実現方法を調べていたところvue-clipboard2というモジュールが見つかったので使ってみることにしました。普通に使えばドキュメント通りに実装すれば動くと思うのですが、Vuetifyのv-dialogの中で使おうとするとかなり手こずったので、動かす方法をまとめます。

環境

  • Vue.js 2.6.11
  • Vuetify 2.2.11
  • TypeScript 3.9.3
  • vue/cli
  • クラスベース記法

vue-clipboard2とは

Inndy/vue-clipboard2

Vueでクリップボードへテキストをコピーできるモジュールです。ボタンをクリックするとテキストをクリップボードにコピーする、といったことが(通常は)簡単にできます。

セットアップは以下の通り。

$ npm install vue-clipboard2
main.ts
import VueClipboard from 'vue-clipboard2';
Vue.use(VueClipboard);

問題

ドキュメントにbootstrapのmodal内だと細工しないと動かないよと書いてありますが、Vuetifyのv-dialogでも当然のように動きませんでした。対処法は探すといくつか見つかりはしますが、TypeScriptを使っている事例がなかなかなく、かなり試行錯誤が必要でした。

解決方法

2つある使い方のうちv-clipboard:copyを使う方法だとどうやっても駄目でした。

$copyTextを使う場合、以下のように実装することで動かすことができました。

<template>
  <v-dialog>
    <v-card>
      <v-card-text>
        <v-text-field :value="textToCopy" readonly/>
        <v-btn icon ref="copyBtn" @click="onCopy"><v-icon>mdi-content-copy</v-icon></v-btn>
      </v-card-text>
    </v-card>
  </v-dialog>
</template>

<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';

@Component
export default class SampleDialog extends Vue {
  textToCopy = 'some text to copy';

  private onCopy(): void {
    const isVue = (x: unknown): x is Vue => x instanceof Vue;
    const { copyBtn } = this.$refs;
    if (!isVue(copyBtn)) {
      return;
    }
    this.$copyText(this.textToCopy, copyBtn.$el);
  }
}
</script>

ハマりどころは$copyTextの第2引数でした。ドキュメントにはcontainerを渡せと雑に書いてはあるものの、実際何型の何を渡せばいいのやら、それらしい可能性を片っ端から試していって上記のコードに辿り着きました。

ボタンにコピー機能を仕込む場合はそのボタンをコンテナとして指定すればいいようです。型はVeturのコード補完ではobject | HTMLElementと出てきますがElement型のオブジェクトを渡すとうまくいきました。

ボタンをElementとして取得する方法も(Vue慣れてる人だと簡単かもしれませんが)結構悩みました。

v-btnタグにref属性を付けるとthis.$refs経由で参照できるようになりますが、その際の型はVue | Element | Vue[] | Element[]です。上記のコードの場合copyBtnで実際に参照できるオブジェクトの型はVueですが(v-btnもコンポーネントである以上Vueを継承している形になっている)、TypeScript的には使う前にタイプガードで型をVueに絞ってあげる必要がありました。(x: unknown): x is Vue => x instanceof Vueのところですね。このチェックを通すことで型が保証され、以降はVue型として扱えるようになります。あとはVue$elプロパティがElementなので、それを$copyTextの第2引数に渡せばTHE ENDにゃん。

ちなみにVueClipboard.config.autoSetContainer = true;という方法も紹介されていますが、こちらはうまくいきませんでした。

もうひとつちなみに、ドキュメントには非同期関数内で使わないでねと書いてありますが、上記コードのonCopyasync付けても動きました。$copyTextPromiseを返すので、処理結果までちゃんと見たい場合はconst result = await this.$copyText(...);のように書くことができます。もちろんthis.$copyText(...).then(...).catch(...);で書いてもOKです。

tomoyuki-sato
LoveLive! driven software developer. フロント・サーバー・インフラ一通りなんとなく。 プライベートでは主にTypeScriptやPython、お仕事では主にPHPやってます。 AWSでMastodonサーバーの運用やってます。
https://mstdn.schoolidol.club/@yazin
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away