Googleフォームカスタマイズで非同期送信する【Ajax】

:::note info
2023年8月に記事後半にてCORSについて追記しました
また最新の情報はhttps://shogo-log.com/google-form-ajax/ の方を更新しております。
:::

静的サイトのフォーム実装問題は、これまで数多の駆け出しweb制作者たちを悩ませてきた問題の１つ。

方法は種々あれど、正直PHPファイル触りたくない...サーバーなんて持っての他である。

今回取材班は、`ついにそのベストプラクティスを発見することに成功した。`

**前提条件**

- jQuery使用
- 結構無理やり（コーポレートサイトよりかアンケート向き）

## ゴール：Googleフォームで静的サイトのフォームを動くようにする

**この記事を読んでできるようになること**

- Googleフォームとの紐付け
- 送信完了後、同ページに「お問い合わせありがとうございました」のメッセージが出せる
- サンクスページに遷移できる

## Googleフォームを作る

まずはGoogleフォームの作成をする。
![screencapture-docs-google-forms-d-e-1FAIpQLSeiu2OXn6fYEOh2-9KW-t0mFtIPBiIbBoSD8RRyaRBxqDNmQQ-viewform-2020-03-02-16_14_38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/472701/d851026e-9b5b-fbd5-2d26-b9d4057c0b53.png)

HTMLフォームで作る予定のものと、当然同じものを作ります。
## input,textareaのnameの値、formのaction先のURLをメモる
chromeの検証機能で探します。
![スクリーンショット 2020-03-02 16.17.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/472701/bf5e5e21-8bce-9ce4-0f6c-d0b13920c462.png)
ラジオボタンだけ、各要素の後に最後に１つだけinputがあるのでそこだけメモります。
![スクリーンショット 2020-03-02 16.23.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/472701/9ebbab5f-c9d0-d609-fa3d-8461462c304f.png)
ここがフォームの送信先になります。

## HTMLフォームを作成
```html

<div class="form-wrapper">
    <form
      action="https://docs.google.com/forms/u/0/d/e/hoge"
      id="form">
      <div class="box">
        <label for="name">お名前</label>
        <input type="text" name="entry.396652" id="name" placeholder="お名前" required>
      </div>
      <div class="box">
        <label for="mail">メールアドレス</label>
        <input type="mail" name="entry.1934980" id="mail" placeholder="メールアドレス" required>
      </div>
      <div class="box">
        <p class="box">お好きなプログラミング言語</p>
        <div class="flex-box"><input type="radio" name="entry.1977612" required value="scala">
          <p>scala</p>
        </div>
        <div class="flex-box"><input type="radio" name="entry.1977612" required value="kotlin">
          <p>kotlin</p>
        </div>
        <div class="flex-box"><input type="radio" name="entry.1977612" required value="basic">
          <p>basic</p>
        </div>

      </div>
      <div class="box">
        <textarea name="entry.181178" id="" cols="30" rows="10" placeholder="自由記述"></textarea><!-- /# -->
      </div>
      <input type="submit" class="submit-btn">
    </form>
    <p class="end-message">お問い合わせありがとうございました</p>
    <p class="false-message">送信失敗です</p>
  </div>
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
```

ここで、**googleフォームと同じ内容のHTMLフォームを作成し、nameとactionの値を合わせます。**

- formタグにid="form"→form内のinputのvalueを取得するためid指定（名前は任意）
- jQuery読み込み→Ajax利用のため

この辺りは後々必要になってくるので、記述しておきます。

## 送信してみる

![スクリーンショット 2020-03-02 15.31.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/472701/7ddb583d-1d67-2a7b-fc0c-f094941296a3.png)
送信すると...

![スクリーンショット 2020-03-02 15.32.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/472701/92271475-8717-66b1-b085-a221c321683f.png)

![screencapture-docs-google-forms-d-1HumHACPZ4M8ugardgscXuArq2IBRg-o-2EFQ2bu3haY-edit-2020-03-02-15_31_35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/472701/e546bea7-469d-5e81-36f6-9b798c4a211a.png)
*おおー受け取れてる！！*
とここで感動してもいいですが、
`いきなりこんなページに飛ばされるのは、ユーザビリティ的に問題があります。`
ここからajax実装で、この画面に遷移しないように手を加えていきましょう。

## お問い合わせ完了メッセージをだす。
```js
$(document).ready(function () {

      $('#form').submit(function (event) {
        var formData = $('#form').serialize();
        $.ajax({
          url: "https://docs.google.com/forms/u/0/d/e/hoge",
          data: formData,
          type: "POST",
          dataType: "xml",
          statusCode: {
            0: function () {
              $(".end-message").slideDown();
              $(".submit-btn").fadeOut();
              //window.location.href = "thanks.html";
            },
            200: function () {
              $(".false-message").slideDown();
            }
          }
        });
        event.preventDefault();
      });

    });

```
まず`serializeでform内のinputのvalueを取り出しgormDataに代入`
その後`urlにgoogleフォーム送信先(action)のurl記入`
そして`jQueryのメソッドで送信後「完了メッセージ表示」`
最後に`event.preventDefault`でsubmitイベントを無効化し遷移させない
という流れ。

![スクリーンショット 2020-03-02 15.39.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/472701/a7c21e05-8a59-fc2e-a1fe-018551111fac.png)
送信後完了メッセージをslideDownで表示することができました。
*`さりげに送信後submit-btnをfadeOutさせているところに、気配りを感じますよね？（二重投票防止）`*
![screencapture-docs-google-forms-d-1HumHACPZ4M8ugardgscXuArq2IBRg-o-2EFQ2bu3haY-edit-2020-03-02-15_39_26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/472701/228628ab-7ef2-a6ae-654c-716a98125626.png)
非同期でもデータ受け取れてます。なんか感動...

## サンクスページ遷移

こちらは一瞬です。

```js

statusCode: {
            0: function () {
              //$(".end-message").slideDown();
              //$(".submit-btn").fadeOut();
              window.location.href = "thanks.html";
            },
```
![スクリーンショット 2020-03-02 15.44.44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/472701/19c53e83-cbbf-7dbc-45ec-d40760f08aac.png)
`window.location.href`で送信後、thanks.htmlに飛ばしています。
![スクリーンショット 2020-03-02 15.50.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/472701/dbc59d81-3028-4b2d-d5a4-80418c835930.png)

css書いてないので、ページ遷移してる感がないですが、一応遷移してますw
![スクリーンショット 2020-03-02 15.50.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/472701/f0ca6657-dc4d-01bb-af02-c51c9ad9a276.png)

![screencapture-docs-google-forms-d-1HumHACPZ4M8ugardgscXuArq2IBRg-o-2EFQ2bu3haY-edit-2020-03-02-16_51_28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/472701/ef420ead-1f93-3193-f0e6-ae39e1954966.png)
こうしてみると、Googleフォームの集計機能すごいっすね...

## 問題点

###　バリデーション

入力必須ぐらいなら、

```html

<input type="text" required>
```
みたいにrequired使えば
![スクリーンショット 2020-03-02 16.54.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/472701/965bb7ec-c99c-28ae-e473-4305e4dda258.png)
デフォでこのように表示してくれるけど、バリデーションはフロントよりサーバーサイド（PHP）でやるのが鉄板らしいから、どうなんでしょうか。

### クロスドメイン・オリジン問題(Cross-Origin Resource Sharing (CORS))

今回の場合、Googleフォームと異なるドメインからフォームを送るはずです。

:::note info 
**クロスドメイン・オリジンとは？**
あるドメインで読み込まれたHTMLページ(sample.com)から、Javascriptなどブラウザ上で動作するプログラミング言語によって、異なるドメイン(sample2.com)のサービスに接続すること
:::

実は、今回のフォームは送信後にconsoleにエラーメッセージが表示されます。

Google Form以外のサイトから https://docs.google.com/forms/... へ直接POSTするとCORSエラーが発生してしまうのです。

CORSポリシーに沿うと、`Access-Control-Allow-Origin`を設定しないといけないのですが、CORSエラーが発生した場合でもFormの内容自体はしっかりとGoogle Formへ送られているので今回は良しとしました。

### ステータスコード0で成功、200でfalseの補足

本来は、HTTP 200番台 Success（成功レスポンス）は、クライアントからリクエストがサーバに送られ理解されて受理された状態で、falseではないはずです。

しかし、ブラウザがクロスオリジンのリクエストに対して正しいCORSヘッダーを受け取らない場合、`実際のHTTPステータスコード（たとえば200）を返す前にリクエストをブロックします。`

この場合、AJAXリクエストのステータスコードとして0が返されます。

`ステータスコード 0`：Google FormsにデータをPOSTすると、正常に送信された場合、ブラウザによるCORSポリシーにより、実際のHTTPステータスコードが返される前にレスポンスがブロックされます。
その結果、jQueryのAJAXリクエストのステータスコードとして0が返されることがよくあります。これは、フォームの送信が実際に成功したことを示すものです。

`ステータスコード 200`：このコードは通常、HTTPリクエストが正常に完了したことを示します。ただし今回の場合、このコードが使用される具体的な理由は、以下のような場面です。
- Google FormsのURLが変わったか、無効になった場合。
- 送信データのフォーマットや名前が誤っている場合。
- Google Formsが何らかの理由で正常なレスポンスを返しているが、期待されるデータの送信や保存が行われていない場合。

では、そもそものCORS制約の回避には何をすればいいのかと言うと、下記3つのどれかです。

**❶サーバーサイドのプロキシを使用する**： 自分のサーバーにエンドポイントを作成し、そのエンドポイントにデータをPOSTします。次に、サーバーサイドのコードはGoogle Formにデータを転送します。この方法で、ブラウザは直接Google Formにリクエストを送信するのではなく、自分のサーバーにリクエストを送信します。そのため、CORS制約は回避されます。

**❷JSONPを使用する**： JSONPはCORS制約を回避する古いテクニックですが、セキュリティ上の懸念があり、今日の多くのシナリオで推奨されていません。また、すべてのサービスやAPIがJSONPをサポートしているわけではありません。

**❸CORSヘッダーを設定する**： これはGoogle Formのエンドポイントに直接アクセス権を持っている場合にのみ可能です。しかし、Google FormはユーザーにCORSヘッダーの設定を許可していないので、この方法は使用できません。
最も実用的なのは、オプション1（サーバーサイドのプロキシを使用する）です。自分のサーバーにエンドポイントを設定し、クライアントからのリクエストを受け取り、それをGoogle Formに転送することでCORSの問題を回避できます。

ブラウザから直にリクエストを送らなければいいと。

とはいえ、サーバー建てるのがめんどくさいからGoogle Form使ってるねんって感じなんですが。

### 受託制作での場合は、相手にフォームを作ってもらう必要あり

Googleアカウント情報をもらうのは煩わしいので、相手にフォームまで作ってもらう必要があります。
少し、相手のITリテラシーが必要かもです。

### [追記]：メール送信機能の実装方法を書きました。
[googleフォーム送信後にメール送信機能を実装する【Google Apps Script】](https://shogo-log.com/google-form-mail/)

**参考記事**

[Googleフォームをカスタマイズして導入、Ajax処理を行う](https://qiita.com/sugitata/items/182173c01e0470a3a4a8)
[Googleフォームを自在にカスタマイズする](https://qiita.com/curioussafuta/items/fa077cbf820faca30598)

自動返信機能参考記事
[Googleフォームをサイトに埋め込み、自動返信メールを送信する(1)](https://qiita.com/shiromegumi/items/8981b4bb7bcb3edd6e60)