対象読者
【Stimulusフレームワーク】入門編2 コピーペーストボタンを作ってみよう【JavaScript】
まで読んだ人

前回と今回の背景

前回までで、PINフィールドをコピーペーストできる機能をStimulusで実装しました。
しかし、コピーペースト機能で利用したClipboard APIは最近のブラウザでは利用可能ですが、古いブラウザでは動きません。大半の方が最近のブラウザを利用しているかと思いますが、やはり古いブラウザユーザも考慮すべきですよね。

そこで今回はブラウザがClipboard APIをサポートしていなかった場合、
コピーペーストボタンが利用できないように制御してみましょう。

引用:MDN web docs

CSSを動的に付与する

利用できないようにどうするか?
ということですが、

  1. 初期は、コピーペーストボタンを非表示
  2. ブラウザが対応していた場合、表示用CSSクラスを付与して、コピーペーストボタンを表示

といった形で実現します。

実際にやってみましょう。
(前回作成したソースコードを土台にします。)

まずは、DIVタグにdata-clipboard-supported-class=”clipboard–supported”を追加します。

  <div data-controller="clipboard" data-clipboard-supported-class="clipboard--supported">
    PIN: <input data-clipboard-target="source" type="text" value="1234" readonly>
    <button data-action="clipboard#copy">Copy to Clipboard</button>
  </div>

そして、BUTTONタグにclass=”clipboard-button”を追加します。

  <div data-controller="clipboard" data-clipboard-supported-class="clipboard--supported">
    PIN: <input data-clipboard-target="source" type="text" value="1234" readonly>
    <button data-action="clipboard#copy" class="clipboard-button">Copy to Clipboard</button>
  </div>

次に、ボタン表示・非表示を再現するため、CSSファイルを作りましょう。
publicディレクトリ配下にmain.cssを作って、下記のとおり記述します。

.clipboard-button {
  display: none;
}

.clipboard--supported .clipboard-button {
  display: initial;
}

これで、ボタンが非表示になったかと思います。実際に動かしてみましょう。
(yarn startでサーバ立ち上げて、ブラウザでhttp://localhost:9000にアクセスです。)

確認できましたら、ブラウザが対応している場合、表示するようJavaScriptの実装に入りましょう。

static classes = [“supported”]をsrc/clipboard_controller.jsに追記しましょう。

追記することにより、Stimulus側で制御できるようになります。

import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  static targets = ["source"]
  static classes = ["supported"]
  
  copy() {
    // ChromeではHTTPS通信でないと使えない
    // navigator.clipboard.writeText(this.sourceTarget.value)
    
    // clipboard APIの代用
    this.sourceTarget.select()
    document.execCommand('copy')
  }
}

つづいて、DOMに接続したタイミング(画面上にDOMが描画されたタイミング)で表示用クラスを付与するようconnect()メソッドを追記します。

import { Controller } from "@hotwired/stimulus"

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

  connect() {
    // もし navigatorオブジェクトにclipboardがあったら
    if ("clipboard" in navigator) {
      // クラスを追記する
      this.element.classList.add(this.supportedClass);
    }
  }
  
  copy() {
    // ChromeではHTTPS通信でないと使えない
    // navigator.clipboard.writeText(this.sourceTarget.value)
    
    // clipboard APIの代用
    this.sourceTarget.select()
    document.execCommand('copy')
  }
}

これで、clipboard APIをサポートしているブラウザの場合、ボタンが表示されるかと思います。

※ もしchromeでうまくいかない場合はclipboard APIの代用でやってみてください。

  connect() {
    // clipboardAPIの代用
    if ("select" in this.sourceTarget) {
      this.element.classList.add(this.supportedClass);
    }
    // if ("clipboard" in navigator) {
    //   this.element.classList.add(this.supportedClass);
    // }
  }

ブラウザで再度動作確認してみましょう。

このように、

HTML: data-コントローラ名-クラスターゲット-class=”クラス名”
JS: static classes = [“クラスターゲット”]

とすることでStimulusコンローラから動的に切り替えることができます。

わざわざHTMLの属性にクラス名指定せず、直接JS内でクラス名してもいいのでは?

Stimulusコントローラ内にクラス名を直書きしてしまうとStimulusコントローラの汎用性を損なってしまいます。原則、固有の値などはHTML側に、汎用的な振る舞いはStimulusコントローラ側に書くよう心がけるとよいかと思います。※現実には難しい場面もあるかもしれません。

あくまでStimulusはDOMによりそっているだけであって、JS <-> DOMを密結合したものではないようです。

いかがでしたでしょうか。
StimulusでCSSを動的に付与できるようになりました。
ありがちなボタンを押したときのデザイン変更などが実装できそうですね。

次回は、スライドショー機能を作り、状態の管理方法を解説したいと思います。