短くまとめると
短く要点をまとめると
- 自分で作ったフォームから回答をGoogleフォームに送信する
- xhrを使った送信がうまくできない場合は、代わりにiframeのsrcにURLパラメーターで回答を付けたGoogleフォームのURLをセットする
- ページ変遷したくない場合はform要素のtarget属性を使う
概要
以下の記事を参考にして自分で作ったフォームからGoogleフォームにJavaScriptで回答を送信しようとしていました。
上記の記事ではJavaScriptで回答を送信する際にCORSエラーが出ても実際には送信できるとあります。しかし、Googleフォームの仕様が変わったのか私の実装が悪かったのか、送信されませんでした。
もう少し調べてみると、このページに
POST data to a Google form with AJAX - stackoverflow
開くだけで回答が送信される、Googleフォームのリンクの作り方が書いてあります。
そこで、非表示にしたiframeのsrcに上記の方法で作ったURLをセットしたところ、回答をうまく送信できました。
また、ページ変遷なしで送信する場合は
FormでPOST処理をしつつ画面遷移させない方法 - Qiita
を参考にします。
具体的な実装
まず、Googleフォームの各input要素のname属性を調べる必要があります。「Googleフォームを自在にカスタマイズする - Qiita」が書かれたときから仕様が変わったのか、input要素がフォームから離れたところにありました。そのため、紹介されているデベロッパーツールを使う方法でname属性を調べるのは困難でした。
そこで、実際にGoogleフォームで回答を送信してみて、送信時の通信内容からname属性を調べます。通信内容の確認は
ChromeデべロッパーツールでPOSTされたパラメータの中身を確認する - ツキミログ
を参考にしてください。例えばこんな感じのデータが送信されています。
entry.166666813: テキストボックスに入力した内容
fvv: 1
draftResponse: [null,null,"9006360873584704621"]
pageHistory: 0
fbzx: 9006360873584704621
entry.数字
がname属性です。entry.数字._sentinel
はname属性の値ではありません。
この場合、フォームは以下のような構成になります。inputのtypeやname属性は適切なものに変更してください。iframeのsrcにセットするhttps://docs.google.com/forms/d/e/<英数字で構成されたID>/formResponse
の部分は、実際にGoogleフォームで回答を送信するページのURLを入れてください。
name属性やURLを書き換えずに以下をそっくりそのまま実行しても動かないので気をつけてください。
<form id="custom_form" target="dummy_send_target">
<input type="text" name="entry.166666813">
<button type="submit">送信</button>
</form>
<p id="words_of_thanks">
アンケートにご協力いただき、ありがとうございました!
</p>
<style>
#words_of_thanks {
display: none;
}
</style>
<script>
function get_textbox_value(name) {
const element = document.querySelector(`input[type=text][name="${name}"]`);
if(element.value) {
return element.value;
} else {
return ""
}
}
function ask_before_changing_page(event) {
event.preventDefault();
event.returnValue = "";
}
document.getElementById("custom_form").addEventListener("submit", function() {
const dummy_send_target = document.createElement("iframe");
dummy_send_target.style.display = "none";
dummy_send_target.name = "dummy_send_target";
document.getElementById("custom_form").appendChild(dummy_send_target);
const form_iframe = document.createElement("iframe");
form_iframe.src = encodeURI(`https://docs.google.com/forms/d/e/<英数字で構成されたID>/formResponse?entry.166666813=${get_textbox_value("entry.166666813")}&submit=Submit`);
form_iframe.style.display = "none";
document.getElementById("custom_form").appendChild(form_iframe);
form_iframe.addEventListener("load", function() {
document.getElementById("custom_form").remove();
document.getElementById("words_of_thanks").style.display = "block";
window.removeEventListener("beforeunload", ask_before_changing_page, false);
});
});
window.addEventListener("beforeunload", ask_before_changing_page);
</script>
上の例では回答を送信する前にページを離脱しようとすると確認のメッセージが表示されます(回答を送信済みの場合、確認メッセージは表示されません)。また、回答の送信後にフォームを削除し、お礼のメッセージを表示します。
get_textbox_value()
関数は、テキストボックスが空のときに「null」という文字列が送信されるのを防ぐために使っています。
私の場合はCustom Elementsを利用している関係で、テンプレートリテラルを使用してget_textbox_value()
関数を1つずつ実行しています。可能ならquerySelectorAll()
を使って自動でURLパラメーターを生成するようにしたほうが良いでしょう。
また、送信後のページ変遷を防ぐための、name属性に「dummy_send_target」が設定されたiframeは上記の例では動的に生成しています。これは、私の場合、最初からiframeがあると読み込み時にCSSが適用されるまでの一瞬、レイアウトが崩れたためです。
上記の例ではテキストボックスを使用していますが、チェックボックスやラジオボタンを利用する場合はvalueには各選択肢のテキストを設定してください。
例)
※ラジオボタンの左の「・」はQiitaの都合で入っているだけなので無視してください。
- はい
- いいえ
Googleフォームで上のようにラジオボタンがある場合、自作するフォームのラジオボタンのvalueはそれぞれ「はい」と「いいえ」になります。
また、Googleフォームで選択肢によって分岐する設定をしている場合や、ラジオボタンに[「その他」を追加]を押して作った「その他」がある場合はうまく動かない可能性があります。ラジオボタンの「その他」に関しては、テキストボックスへの入力を求められる『「その他」を追加』は使用せず、通常の選択肢を作るのと同じようにして「その他」というラベルを付ければ大丈夫です。つまり、下の画像内の上の例はうまく動かない可能性があり、下の例は動くということです。
最後に、今回紹介した方法は2020年11月5日の時点では使えますが、今後仕様が変わって使えなくなる可能性もあるので注意してください。
参考にした記事一覧
記事内で紹介したページです。