非同期時の送信ボタン制御について

# 記事の内容
非同期での送信ボタン制御を実装する中で色々と躓いたため、備忘録も兼ねて記録として投稿しました。

今回は、XMLHttpRequesを使用して JavaScript から非同期的にデータを送信する方法をご紹介いたします。

# 完成形

[![Image from Gyazo](https://i.gyazo.com/0b3d2fa3696586e4a03339fcda9dc771.gif)](https://gyazo.com/0b3d2fa3696586e4a03339fcda9dc771)

# 完成形のコード
modelオプションに設定している`@pdf`は、コントローラで定義しているインスタンス変数になります。
`gem`は`wicked_pdf`を使用しており、ここではPDFファイルを生成する処理を記述しております。

https://qiita.com/inoue9951/items/4498e5d130702d884352

※ コントローラ、フォーム部分は一部割愛しております。

```ruby:order_page.html.erb
<%= form_with model: @pdf, url: "#{mypage_history_path}?order_id=#{@order.id}", local: true do |f| %>
  <button type="submit", id='pdf-btn', class="btn btn-primary btn-lg btn-receipt">領収書</button>
<% end %>
```

```javascript:order_page.html.erb
<script>
$('form').submit(function(){
  element = $("#pdf-btn")[0]
  element.disabled = true;
  element.innerText = "ダウンロード中";

  const xhr = new XMLHttpRequest();
  xhr.open('GET', this.action);
  xhr.responseType = 'arraybuffer';
  xhr.onload = function() {
    if (this.status === 200) {
      const blob = new Blob([this.response], {type:"application/pdf"});
      const link = document.createElement('a');
      link.href = window.URL.createObjectURL(blob);
      link.download = "<%="ryoshusyo-No#{@order.id}.pdf"%>";
      link.click();
    }
    element.disabled = false;
    element.innerText = "領収書";
  };
  xhr.send(f);
  return false;
});
</script>
```

# 実装開始
ここから実装内容を説明していきます。

## ①多重送信防止の対処
まず初めに、送信ボタンに付与したIDとマッチする要素を取得して変数に格納しています。
その後、多重送信防止のため、`disabled`を付与して非活性にし、ボタンの表記も変更しています。

```javascript
element = $("#ryosyusyo-android-pdf-btn")[0]
element.disabled = true;
element.innerText = "ダウンロード中";
```

## ②`XMLHttpRequest`オブジェクトの生成
次にブラウザとサーバ間で通信を行うために`XMLHttpRequest`オブジェクトを生成しています。

```javascript
const xhr = new XMLHttpRequest();
```

## ③リクエスト方法を設定
**HTTPメソッド**と**アクセスする場所**を指定します。

```javascript
xhr.open('GET', this.action);
```

**第1引数**には、使用する Httpリクエストメソッドを指定します。
今回は、データを取得するだけなので`GET`メソッドを指定しています。

**第2引数**には、リクエスト先のURLアドレスを指定します。
ここでは`this.action`で`form`の`action`属性値であるURLアドレスを指定しています。

## ④データ型を指定
レスポンスに含まれているデータの型を指定します。

```javascript
xhr.responseType = 'arraybuffer';
```

今回は、バイナリデータを含む`JavaScript`の`ArrayBuffer`をデータ型として指定しています。

# ⑤PDFファイルのダウンロード
ダウンロードするための処理は下記コードになります。

```javascript
xhr.onload = function() {
  if (this.status === 200) {
    const blob = new Blob([this.response], {type:"application/pdf"});
    const link = document.createElement('a');
    link.href = window.URL.createObjectURL(blob);
    link.download = "<%="ryoshusyo-No#{@order.id}.pdf"%>";
    link.click();
  }
  element.disabled = false;
  element.innerText = "領収書";
};
```

## 1, `onload`イベント

`onload`メソッドを使用して**レスポンスを受け取った後**に function 内の処理を実行させています。

```javascript
xhr.onload = function() {
```

## 2, `Blob`オブジェクトを生成
`Blob`とは、`Binary Large Object`の略でバイナリデータを扱うためのクラスです。

その`Blob`を使用するとユーザーに動的にファイルをダウンロードさせることが可能になります。
そのため、まず初めに`Blob`オブジェクトを生成しています。

```javascript
if (this.status == 200) {
  const blob = new Blob([this.response], {type:"application/pdf"});
```

**第1引数**には、データの配列を設定します。
今回は、`xhr`変数の`response`に格納されている`ArrayBuffer`の配列データを渡しています。

**第2引数**には、ファイルタイプを指定します。
今回は、PDF形式のファイルをダウンロードするため、`type:"application/pdf"`としています。

## 3, ダウンロードリンクを作成
次に`Blob`データをダウンロードする処理を実装していきます。

```javascript
const link = document.createElement('a');
link.href = window.URL.createObjectURL(blob);
link.download = "<%="ryoshusyo-No#{@order.id}.pdf"%>";
link.click();
```
1. `createElement('a')`で**リンクタグ**を生成し、`link`変数に格納しています。
1. `URL.createObjectURL()`を使用し、引数に先ほど生成した`blob`オブジェクトを渡すことで**オブジェクトURL**を生成し、href属性値に格納しています。
1. ダウンロード属性にファイル名を設定しています。
今回の場合、ファイル名は「ryoshusyo-No{注文番号}.pdf」となります。
1. 最後に先ほど生成したリンクタグを`click`イベントで発火させることで、PDFファイルをダウンロードしています。

## 4, 元の状態に戻す
ダウンロードボタンをデフォルトの状態に戻すための記述を行っています。

```javascript
element.disabled = false;
element.innerText = "領収書";
```

## ⑥HTTPリクエストを送信
`XMLHttpRequest`の`send`メソッドを使用して`ajax`通信を実行しています。

```javascript
xhr.send();
```

ここで、③で設定したHTTPメソッドとリクエスト先URLをもとに、リクエスト送信を行っています。

設定内容 => `xhr.open('GET', this.action);`


## ⑦イベント伝播を制御
最後に親要素へのイベント伝播を止めるための記述を行っています。

```javascript
return false;
```

## 終わりに
恥ずかしながら今まで`XMLHttpReques`を利用したことがなく、初見は何がなんだか分からない状態でした、、
今回記事にしたことで理解は深まりましたが、不明点も多々あり、、。
今後も学びを大切に日々アウトプットしていきます！

## 参考記事

https://javascript.keicode.com/newjs/how-to-use-formdata.php

https://reffect.co.jp/html/xmlhttprequest-basic

https://www.sejuku.net/blog/67735

https://www.sejuku.net/blog/19754