[JavaScript]aタグのダブルクリック対策をしたい

# はじめに

- aタグのリンクを押したときにformのsubmitを実行する、というHTMLがあった
  - マークアップが先に作られておりこれを担保しつつ、ボタン機能を持たせる必要があった
- 例として以下のようなマークアップで「ログイン」ボタンが作られていたとする

```html
<p>
	<a href="{{ route('lognin') }}" onclick="event.preventDefault(); document.getElementById('login').submit();">
		ログイン
	</a>
</p>
<form id="login" action="{{ route('login') }}" method="POST">
    @csrf
</form>
```


処理としては

1, aタグで作られたログインボタン押す
2, `login`の要素を持つformタグ内でsubmit()を実行
3, actionで指定したルートへPOSTする

といった感じで処理が走る状態

## やりたいこと

今回はこのaタグが**ダブルクリックされない**ようにして、余計なリクエストがサーバーに投げられるのを防ぎたかった

## 環境

- （Laravel 8.0）
- jQuery 2.2.4
 
## やったこと

いくつか記事を参考させていただきました。

### 1, ２回目クリック以降はdisable属性を付与

- aタグには対応しておらず、断念
    - inputタグ、buttonタグなどにはdisabledが対応

[https://www.nishishi.com/javascript-tips/button-disabled.html#button-disabled0-source](https://www.nishishi.com/javascript-tips/button-disabled.html#button-disabled0-source)

### 2, 2回目クリック以降は何も処理をしない

- aタグのonClick→直接JSに入っているので、formのsubmitをする、という処理ができず断念

[https://stackoverflow.com/questions/15622100/how-can-i-disable-href-if-onclick-is-executed](https://stackoverflow.com/questions/15622100/how-can-i-disable-href-if-onclick-is-executed)

### 3, 2回目クリック以降はalertを出す

- 2回目のクリックはPOST送信されない対応はできた
    - ただ、他の画面と比べてフロント制御方法としてイレギュラー対応になるため却下

[https://qiita.com/tabo_purify/items/7d47656bd7c6332efe8a#%E3%83%AA%E3%83%B3%E3%82%AF%E3%81%AE%E9%80%A3%E7%B6%9A%E3%82%AF…](https://qiita.com/tabo_purify/items/7d47656bd7c6332efe8a#%E3%83%AA%E3%83%B3%E3%82%AF%E3%81%AE%E9%80%A3%E7%B6%9A%E3%82%AF%E3%83%AA%E3%83%83%E3%82%AF%E9%98%B2%E6%AD%A2)

## 結果

機能しない、もしくは実装方法が気に入らないという理由で自分で作ることにしました。。。

## 対策

調べた記事にあった要素をかき集めて以下の流れで対応としました


1, クリック時にJS(jQuery)のイベント発火
2, イベント内でaタグのリンク機能を無効化する
3, ブラウザバック対応としてリンク機能を再度有効化

参考：

[https://freeladay.com/?p=2273#CSSadisabled](https://freeladay.com/?p=2273#CSSadisabled)

- `pointer-events`というstyleを初めて知った

[https://developer.mozilla.org/ja/docs/Web/API/Element/setAttribute](https://developer.mozilla.org/ja/docs/Web/API/Element/setAttribute)

[https://developer.mozilla.org/ja/docs/Web/API/Element/removeAttribute](https://developer.mozilla.org/ja/docs/Web/API/Element/removeAttribute)

- JSで属性を付与したり、削除したりできるのかと知った

---

これらを使ってコードに落とし込んだのが以下です。


```index.html
...
<-- jQueryを発火させるclassを追加 -->
<p class="disabled">
	<a href="{{ route('lognin') }}" onclick="event.preventDefault(); document.getElementById('login').submit();">
		ログイン
	</a>
</p>
<form id="login" action="{{ route('login') }}" method="POST">
    @csrf
</form>
...
<-- JS読み込み(読み込み方はお任せします) -->
<script src="{{ mix('js/app.js') }}"></script>
...
```

```app.js
$(document).on('click','.disabled',function (event) {
		// イベント発火箇所から一番近いaタグを取得
        const target = event.target;
        const $a = $(target).closest('a');

		// aタグの有無確認
        const hasStyle = $a[0].hasAttribute('style');
        let currentStyle = null;

        // aタグ無効化
        if (hasStyle) {
            currentStyle = $a[0].getAttribute('style');
            $a[0].setAttribute('style', currentStyle + '; pointer-events: none;');
        } else {
            $a[0].setAttribute('style', 'pointer-events: none;');
        }

        // 時間経過(5s後)でaタグ無効化を解除
        setTimeout(function () {
            if (hasStyle) {
                $a[0].setAttribute('style', currentStyle);
            } else {
                $a[0].setAttribute('style', 'cursor: pointer;');
            }
        }, 5000);
    });
```

ポイントは**ブラウザバック対応**のためにsetTimeoutでstyleを初期化している点ですね。

※ちなみに`$a[0]`の`0`が必要であるのはeventに複数要素が入っていることから必要になります。
　console.logで`event.target`を出力してみるとわかると思います。

# おわりに

- なんともダブルクリック対応やるにしては冗長感が否めないのですが、今のところこの形で収まりました。
    - 特に現状styleの有無をチェックする必要がある点で冗長になっている感覚。
- プロジェクトの都合でこの形をとっているので、参考になるかはわかりませんが一つの実装方法の選択肢、程度に思ってもらえればいいかと思います。
- 他にも「何かこんな形でできるのでは？」とかあれば気軽に教えていただけると幸いです。

最後までお読みいただきありがとうございました。