【GAS】Bootstrapのモーダルダイアログを使いまわす

## はじめに
GASでWebサービスを作る際、リダイレクト不可の影響でフォーム送信後にページリロードをしたりすると同じデータが送られてしまうため若干面倒です。
そうならないためにはgoogle.script.runで遷移せずに処理をするのが良いのですが、確認や完了、エラーなどのダイアログは通常のシステムダイアログだと表示が味気なかったりカスタマイズしにくかったりするので、Bootstrapのものを使うことにしました。
今回はBootstrap5を使用しています。
Bootstrapのダイアログは基本的に画面に１つだけ開く想定で作られているので、多重開き防止（開いたら現在開いているものを閉じる）などの機能を別途組み込みます。


## 準備
まずはダイアログを管理するDialogInfoクラスを作成します。
モーダルダイアログは、hideでフェードアウトしている最中にshowで別のダイアログを出そうとするとレイアウトがおかしくなったりしてしまうため、ちゃんと閉じてから次のダイアログを開く、という形で実装しています。
```html:dialog_info.html
<script>
/*=================================================================================
 Bootstrapのモーダルを管理するクラス
 多重開き防止（他に開いているものがあれば閉じてから表示）が可能
=================================================================================*/
class DialogInfo{

  //現在開いているダイアログを管理する変数（二重開きの防止）
  static #currentDialog = null;

  #id;
  #modal;

  //コンストラクタ
  //option:モーダルのオプション（静的バックドロップなどの設定）
  constructor(id,option){

    this.#id = id;
    this.#modal = new bootstrap.Modal(document.getElementById(id),option)

    //モーダルが完全に閉じたのときの設定
    document.getElementById(id).addEventListener(
      'hidden.bs.modal', 
      function (event) {

        if(DialogInfo.#currentDialog != null){

          //次に開くダイアログが設定されている場合、開く
          if(DialogInfo.#currentDialog.getId() != event.currentTarget.id){
            DialogInfo.#currentDialog.show();
          
          //自分のダイアログだった場合参照を消してそのまま閉じる
          }else{
            DialogInfo.#currentDialog = null;
          }
        }
      }
    );


  }

  //ダイアログのIDを取得(getElementByIdなどで使う)
  getId(){
    return this.#id;
  }

  //ダイアログを開く
  show(){

    //自分以外のダイアログが開いている場合は、閉じてから開くようにする
    if(DialogInfo.#currentDialog != null && DialogInfo.#currentDialog != this){
      DialogInfo.#currentDialog.hide();

    //現在開いているダイアログがない場合、そのまま開く
    }else{

      this.#modal.show();
    }
    DialogInfo.#currentDialog = this;
  }

  //ダイアログを閉じる
  hide(){
    this.#modal.hide();
  }

}


</script>
```

その後各ダイアログ毎にhtmlを作成します。今回はエラーダイアログのみについて記載します。
下記の様に、Bootstrapのブロックとそれを表示するJavascriptをひとまとめにします。
```html:alert_dialog.html
<!-- Modal -->
<div class="modal fade" id="alertDialog" tabindex="-1" aria-labelledby="alertDialogLabel" aria-hidden="true">
  <div class="modal-dialog">
    <div class="modal-content">
      <div class="modal-header">
        <h5 class="modal-title" id="alertDialogLabel">入力エラー</h5>
        <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
      </div>
      <div class="modal-body" id="alertDialogBody">
      </div>
      <div class="modal-footer">
        <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">戻る</button>
      </div>
    </div>
  </div>
</div>
<script>

var _alertDialog = new DialogInfo("alertDialog",{});

//======================================================================================================
// エラーダイアログ表示
//======================================================================================================
function alertDialog(body){

  document.getElementById("alertDialogBody").innerHTML = body;

  _alertDialog.show();

}

```

## 呼び出し
ダイアログ実装したいhtmlの中に表示できるよう、html最下部（BootstrapのJSを読み込んだ後）に記述します。
```html:読み込ませたいhtml
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/js/bootstrap.bundle.min.js" integrity="sha384-ygbV9kiqUc6oa4msXn9868pTtWMgiQaeYH7/t7LECLbyPA2x65Kgf80OJFdroafW" crossorigin="anonymous"></script>  
<?!= HtmlService.createHtmlOutputFromFile('dialog_info').getContent(); ?>
<?!= HtmlService.createHtmlOutputFromFile('alert_dialog').getContent(); ?>
</body>
</html>
```
あとはフォーム送信処理や確認のところでダイアログを呼び出せば完了です。
```javascript
var message = "名前が入力されていません。";
alertDialog(message);
```

アラートダイアログ以外に確認ダイアログや完了ダイアログを出したい場合は、ダイアログ毎にhtmlを作成して下部で呼び出す設定をしましょう。
```html:読み込ませたいhtml
  <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/js/bootstrap.bundle.min.js" integrity="sha384-ygbV9kiqUc6oa4msXn9868pTtWMgiQaeYH7/t7LECLbyPA2x65Kgf80OJFdroafW" crossorigin="anonymous"></script>  
		<?!= HtmlService.createHtmlOutputFromFile('dialog_info').getContent(); ?>
		<?!= HtmlService.createHtmlOutputFromFile('confirm_dialog').getContent(); ?>
		<?!= HtmlService.createHtmlOutputFromFile('alert_dialog').getContent(); ?>
		<?!= HtmlService.createTemplateFromFile('complete_dialog').evaluate().getContent(); ?>
  </body>
</html>
```
この設定の場合、開きたいダイアログをshow()で呼び出せば、もし現在開いているダイアログがある場合は自動的に閉じられ、完全に閉じた後にちゃんと開くようになります。
## 終わりに
GASとBootstrapは相性がとてもいいので、フォームなどのカスタマイズやテーブル操作含めて作っていくと楽しいですよ。