[Vue]submit時のダブルクリック対策

# はじめに

今のプロジェクトではダブルクリック対策を目的に以下を参考にさせていただき、jQuery による実装をしている。

https://qiita.com/ponko2/items/fd7ac38b964e10f16f52#%E9%80%81%E4%BF%A1%E3%83%9C%E3%82%BF%E3%83%B3%E3%83%80%E3%83%96%E3%83%AB%E3%82%AF%E3%83%AA%E3%83%83%E3%82%AF%E3%81%A7%E3%81%AE%E4%BA%8C%E9%87%8D%E9%80%81%E4%BF%A1%E9%98%B2%E6%AD%A2

ただ、基本フロントはVueをメインで使っているので、JS制御にjQuery とVueが入り混じっている状態なので社内でも「脱jQuery したいよね」って話が上がっている中だった。

そこで、この「ダブルクリック対応」をjQuery からVueへリプレイスしよう、と思った。

## 環境

- サーバー：Laravel 8系
- フロント：Vue.js 2系, jQuery

# 現状

上記リンクのソースコードまんまです

> resources\js\app.js
> 

```javascript
$('form :submit').click(function (event) {
  var TIMEOUT = 10000;
  var target  = event.target;
  var $form   = $(target).closest('form');
  var $submit = $form.find(':submit');

  // clickしたsubmitの値をhiddenに保存
  var $hidden = $('<input/>', {
    type: 'hidden',
    name: target.name,
    value: target.value
  }).appendTo($form);

  event.preventDefault();
  event.stopPropagation();

  // 全てのsubmitを無効化
  $submit.prop('disabled', true);

  // 時間経過でsubmitの無効化を解除
  setTimeout(function () {
    $hidden.remove();
    $submit.prop('disabled', false);
  }, TIMEOUT);

  $form.submit();
});
```

挙動としては以下の通り

- `<form>…</form>`で囲まれた`<input type=“submit”>`ボタン、`<button type=”submit”>…</button>`が押す
- `<input type=“submit”>`にdisabled属性が付与され、ボタンが押せなくなる
- `setTimeout`で指定した時間経過したら、disabled属性が消え、ボタンが押せるようになる

## 困ること

このjQueryを使ってダブルクリック対策をしようとすると

- プロジェクト全体に影響するjsファイルに実装する（`resources\js\app.js`にこれを書く）
- ダブルクリック対策が必要Bladeにjsファイルを読み込ませる

るが、1個目は想定外の挙動（HTML属性が動かないなど）、2個目は運用上手間（読み込み忘れによるダブルクリック対策漏れなど）が起こりうる

※実際に1個目の「想定外の挙動」については以下の記事の通り、formactionが動かない事象が発生しました。。。

https://qiita.com/TkTkTkTkTako/items/5e0d3df682ae5b114d59

# **どうするか**

じゃあどうするかという話。結論は**「Vueでダブルクリック対策できるコンポーネント作成とその利用」**です。

## jQueryによるダブルクリック対応を削除

まずは`resources\js\app.js`のjQuery による実装を削除

## Vueでダブルクリック対応できるコンポーネント作成

以下のようにボタンクリック時にdisable属性を付与するコンポーネントを作成

> resources\js\components\common-submit-component.vue
> 

```vuejs
<template>
  <div>
    <input
      type="submit"
      :value="buttonName"
      @click.prevent.stop="onSubmit"
    >
  </div>
</template>

<script>
export default {
  props: {
    buttonName: {
      type: String,
      default: '送信する'
    }
  },
  methods: {
    onSubmit(event) {
	  const timeoutMillis = 5000;
      const target = event.target;
      let form = target.closest('form');

      // フロントのバリデーションに引っかかるならエラー表示
      if (!form[0].reportValidity()) {
        return false;
      }

      // submitを無効化
      console.log('disabled submit');
      target.disabled = true;
      // 時間経過でsubmitの無効化を解除
      setTimeout(() => {
        target.disabled = false;
        console.log('enabled submit');
      }, timeoutMillis);
      form.submit();

	  // eslint対応. なくても大まかな挙動は問題ない
      return true;
  }
};
</script>
```

## Bladeでコンポーネントを使う

> resources\views\samples\index.blade.php
> 

```html
<common-submit-component></common-submit-component>
```

これで挙動としては

- `<form>…</form>`で囲まれた`<input type=“submit”>`ボタン、`<button type=”submit”>…</button>`が押す
- 押した`<input type=“submit”>`にdisabled属性が付与され、ボタンが押せなくなる（※）
- `setTimeout`で指定した時間経過したら、disabled属性が消えボタンが押せるようになる

となり、今までと同じ挙動をします。

## ※注意

元々のjQueryでは画面上にあるすべてのsubmitボタンをdisabledにしていましたが、今回のコンポーネント化では**押したsubmitボタンのみdisabled**になります。

「全submitをdisabled」対応をするなら先ほどの参考リンクの処理をコンポーネント内で処理する形になるんですが、そうすると**上述の「想定外の挙動（HTML属性が動かないなど）」に直面する**と思うので、本末転倒です・・・

なので、対応選択肢としては

- 「押したボタンだけdisabledでOK」と**割り切る**
    - 要件が「ダブルクリックによる2重リクエスト防止」であればこれで十分かと思います。
- **ボタン押したときの挙動をスピナー（ローディング中のグルグル）表示**などにして画面操作できないようにする

の2つかなと思います。

※後者は以下のコメント箇所にモーダル表示／非表示の処理を書けばOKです

```vuejs
...
<script>
export default {
	...
  methods: {
    onSubmit(event) {
	  const timeoutMillis = 5000;
      const target = event.target;
      let form = target.closest('form');

      // フロントのバリデーションに引っかかるならエラー表示
      if (!form[0].reportValidity()) {
        return false;
      }

      // TODO: スピナー表示
			
      // 時間経過でsubmitの無効化を解除
      setTimeout(() => {
	      // TODO: スピナー非表示
			
      }, timeoutMillis);
      form.submit();

      return true;
  }
};

```

# おわりに

- 当初課題の「ダブルクリック対策漏れ」については、**submitするときは必ずこのコンポーネントを使うことで漏れはなくなります**。
- さらにもう１つの課題だった「想定外の挙動（HTML属性が動かないなど）」も当初の様に **`<form>`や`<input>`の属性を書き換えたりしない** ので解決できます。
- なにより、`resources\js\app.js`からJS処理を消せるのはうれしい。。。
- スピナーでのダブルクリック対策では`// TODO: スピナー非表示`処理が**submit後のブラウザバック時や遷移先などで変なタイミングで実行されないか気になりましたが、そこは問題なさそう**でした。
- 上述のスピナー表示でのダブルクリック対策にすることで、**`<button>`タグ、`<a>`タグもこのテンプレート内で書いて、各タグクリック時に`@click.prevent.stop="onSubmit"`を呼び出せばダブルクリック対策を共通化できる**ので、スピナー表示が最適解な気がします。別でまとめます。
- しかし、Vueのコンポーネントの強さを再実感した対応でした。