【Salesforce Visualforce】実行ボタンの2度押し防止(スピナー)

## はじめに

Visualforceで画面を作成していて、実行ボタンが2度押しされたことはないでしょうか？

ブラウザのタブで実行中のクルクルが回ってますが、あれ押したかな？動いているのかな？とユーザはお構いなしにボタンを何度も押すことが多いようです。

その対策として、ユーザにも分かりやすいように、処理中だと画面で分かるようにし
かつ、2重実行が出来ないようにしたいと思います。

※Lightning Web Conponent等を利用すれば簡単にできそうですが
　すでにVisualforceで作成してしまっているので、そちらで実現をしたいと思います。

いろんなやり方はあると思いますが、今回は動画ファイル不要のCSSだけのもので実現をしています。

## 実装概要
実行ボタンを押した時に、実行中を示す(スピナー)を表示させ、ユーザの2度押しを防止する。

## 実装イメージ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2091355/f72327fe-584b-4e71-4220-498e58012de8.png)

## サンプルコード
```
<apex:page lightningStylesheets="true" docType="html-5.0" recordSetVar="tests" standardController="test__c" extensions="TestDataCreate">

    <!-- jQuery -->
    <apex:includeScript value="{!$Resource.jquery}"/>

    <apex:form >
        <apex:pageMessages />
    	<apex:pageBlock >
            <apex:pageBlockSection >
            	<apex:selectList value="{!year}" size="1" label="年度">
                    <apex:selectOptions value="{!yearList}" />
                </apex:selectList>
            </apex:pageBlockSection>
            <apex:pageBlockButtons location="bottom">
            	<apex:commandButton value="作成" onclick="return confirmExecAction();" action="{!create}"/>
            </apex:pageBlockButtons>
        </apex:pageBlock>
    </apex:form>

	<div id="overlay">
		<div class="cv-spinner">
			<span class="spinner"></span>
		</div>
	</div>

    <script type="text/javascript">
    	// action実行確認
    	function confirmExecAction() {
        	if (window.confirm("実行します。よろしいですか？")) {

                $("#overlay").fadeIn(500); //二度押しを防ぐloading表示
                setTimeout(function() {
                    $("#overlay").fadeOut(20000);
                }, 20000);

                return true;
            }

            return false;
        }
    </script>

    <style>  
        #overlay{
            position: fixed;
            top: 0;
            left: 0;
            z-index: 999;
            width: 100%;
            height:100%;
            display: none;
            background: rgba(0,0,0,0.6);
        }
        .cv-spinner {
            height: 100%;
            display: flex;
            justify-content: center;
            align-items: center;
        }
        .spinner {
            width: 80px;
            height: 80px;
            border: 4px #ddd solid;
            border-top: 4px #999 solid;
            border-radius: 50%;
            animation: sp-anime 0.8s infinite linear;
        }
        @keyframes sp-anime {
            0% { transform: rotate(0deg); }
            100% { transform: rotate(359deg); }
        }
        .is-hide{
            display:none;
        }
    </style>
</apex:page>
```

## 説明
```
<apex:pageBlockButtons location="bottom">
	<apex:commandButton value="作成" onclick="return confirmExecAction();" action="{!create}"/>
</apex:pageBlockButtons>
```
onclickで実行確認のダイアログ、スピナー実行のjavascriptを呼び出しています。


```
<!-- jQuery -->
<apex:includeScript value="{!$Resource.jquery}"/>
```
jQueryを使用している為、登録済み静的リソースを呼び出し


```
<style>  
    #overlay{
        position: fixed;
        top: 0;
        left: 0;
        z-index: 999;
        width: 100%;
        height:100%;
        display: none;
        background: rgba(0,0,0,0.6);
    }
    .cv-spinner {
        height: 100%;
        display: flex;
        justify-content: center;
        align-items: center;
    }
    .spinner {
        width: 80px;
        height: 80px;
        border: 4px #ddd solid;
        border-top: 4px #999 solid;
        border-radius: 50%;
        animation: sp-anime 0.8s infinite linear;
    }
    @keyframes sp-anime {
        0% { transform: rotate(0deg); }
        100% { transform: rotate(359deg); }
    }
    .is-hide{
        display:none;
    }
</style>
```
spinner用cssです。

```
<script type="text/javascript">
    // action実行確認
    function confirmExecAction() {
        if (window.confirm("実行します。よろしいですか？")) {

            $("#overlay").fadeIn(500); //二度押しを防ぐloading表示
            setTimeout(function() {
                $("#overlay").fadeOut(20000);
            }, 20000);

            return true;
        }

        return false;
    }
</script>
```
確認ダイアログを表示
　・OK　　　　　･･･　spinnerを表示させ、actionを実行
　・キャンセル　･･･　actionを中止

今回処理時間が長めなのでtimeout値を多めにとっています。
CSSは直接書いていますが、複数の機能で使用する場合は、共通のCSSとして静的リソースに登録をしてしまったほうがよいかと思います。

## その他
今回は使用していないので未検証ですが、ajaxを利用する場合は以下のようなコードになります。

```
<script>
    $(".sbmitbtn").on("click", function(){
        $(document).ajaxSend(function() {
            $("#overlay").fadeIn(500);
        });
        $.ajax({
            type: 'GET',
            success: function(data){
                console.log(data);
            }
        }).done(function() {
            setTimeout(function(){
                $("#overlay").fadeOut(500);
            },3000);
        });
        return false;
    });
</script>
```