google.script.runでHTMLフォームからGASを呼び出す方法

第4回で作成した入力フォームから google.script.run でGAS側の saveRequest(data) を呼び出し、作業依頼シートへ登録します。
今回は、登録中の表示、ボタンの無効化、登録後のフォーム初期化に加え、成功・失敗ハンドラーを実装します。
シリーズ全体の構成と完成画面は第0回で紹介しています。
1. 今回作るもの
| ファイル | 用途 |
|---|---|
Code.gs | saveRequest(data) で登録データを処理する |
index.html | 入力フォーム。script.html を読み込む |
style.html | 登録中、成功、エラー表示の見た目を追加 |
script.html | google.script.run でGASを呼び出す |
2. 完成イメージ
- フォームの入力値をJavaScriptオブジェクトにできる
- 入力不足を画面側で確認できる
- 登録中にボタンを無効化できる
google.script.runでsaveRequest(data)を呼び出せる- 成功時にメッセージを表示し、フォームを初期化できる
- 失敗時にエラーメッセージを表示できる

3. 前回までの状態
第4回では、HTML Serviceを使って作業依頼受付フォームを表示できるようにしました。
第5回の starter.zip には、第4回完了時点の Code.gs、index.html、style.html を入れています。
4. 変更するファイル
第4回から更新するのは index.html と style.html です。新しく script.html を作成します。Code.gs は第4回から変更せず、既存の saveRequest(data) を使います。
script.html には、入力値の取得、入力チェック、GAS関数の呼び出し、結果の表示をまとめます。
5. データの流れ
入力フォーム ↓ script.htmlで入力値を集める ↓ google.script.runでsaveRequest(data)を呼び出す ↓ Code.gsがappendRow()で作業依頼シートに1行追加する ↓ Code.gsが結果オブジェクトを返す ↓ script.htmlが結果に応じたメッセージを表示する
google.script.run は、HTML Serviceの画面からGAS側の関数を呼び出す仕組みです。GAS側の処理を待つ間も画面を操作でき、結果は後からハンドラーで受け取ります。詳しくはGoogle公式の通信ガイドを参照してください。
6. 貼り付けるコード
index.html
index.html は、第4回で作成したファイルを次のように更新します。フォームに id を付け、script.html を読み込む形です。
第4回からの変更点は次のとおりです。
<form>にid="requestForm"を追加- 顧客、件名、依頼内容に
requiredを追加 - 顧客の
<option>にdata-nameを追加 - 登録ボタンを
type="submit"に変更し、id="submitButton"を追加 - メッセージ欄に
id="formMessage"を追加 <?!= include('script'); ?>を追加
data-name により、JavaScript側で「顧客コード」と「顧客名」の両方を取得できます。
<!doctype html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<base target="_top">
<?!= include('style'); ?>
</head>
<body>
<main class="page">
<section class="form-panel" aria-labelledby="page-title">
<p class="eyebrow">作業依頼・受付管理Webアプリ</p>
<h1 id="page-title">作業依頼受付フォーム</h1>
<p class="lead">必要事項を入力して、作業依頼を登録します。</p>
<form id="requestForm">
<div class="field">
<label for="customer">顧客</label>
<select id="customer" name="customer" required>
<option value="">選択してください</option>
<option value="C001" data-name="サンプル商事">サンプル商事</option>
<option value="C002" data-name="テスト工業">テスト工業</option>
<option value="C003" data-name="デモサービス">デモサービス</option>
</select>
</div>
<div class="field">
<label for="title">件名</label>
<input id="title" name="title" type="text" placeholder="例:資料作成の依頼" required>
</div>
<div class="field">
<label for="description">依頼内容</label>
<textarea id="description" name="description" rows="6" placeholder="依頼したい作業内容を入力してください" required></textarea>
</div>
<div class="field">
<label for="assignee">担当者</label>
<input id="assignee" name="assignee" type="text" placeholder="例:山田">
</div>
<button id="submitButton" type="submit">登録する</button>
<p id="formMessage" class="message">入力後に登録ボタンを押してください。</p>
</form>
</section>
</main>
<?!= include('script'); ?>
</body>
</html>script.html
<script>
const form = document.getElementById('requestForm');
const submitButton = document.getElementById('submitButton');
const message = document.getElementById('formMessage');
form.addEventListener('submit', (event) => {
event.preventDefault();
const requestData = buildRequestData();
const validationMessage = validateRequestData(requestData);
if (validationMessage) {
showMessage(validationMessage, 'error');
return;
}
setSubmitting(true);
showMessage('登録しています。しばらくお待ちください。', 'info');
google.script.run
.withSuccessHandler(handleSaveSuccess)
.withFailureHandler(handleSaveFailure)
.saveRequest(requestData);
});
function buildRequestData() {
const customerSelect = document.getElementById('customer');
const selectedOption = customerSelect.options[customerSelect.selectedIndex];
return {
customerCode: customerSelect.value,
customerName: selectedOption ? selectedOption.dataset.name || '' : '',
title: document.getElementById('title').value.trim(),
description: document.getElementById('description').value.trim(),
assignee: document.getElementById('assignee').value.trim(),
};
}
function validateRequestData(data) {
if (!data.customerCode) {
return '顧客を選択してください。';
}
if (!data.title) {
return '件名を入力してください。';
}
if (!data.description) {
return '依頼内容を入力してください。';
}
return '';
}
function handleSaveSuccess(result) {
setSubmitting(false);
if (!result || !result.success) {
showMessage(result && result.message ? result.message : '登録に失敗しました。', 'error');
return;
}
form.reset();
showMessage('登録しました。', 'success');
}
function handleSaveFailure(error) {
setSubmitting(false);
showMessage(error && error.message ? error.message : '登録中にエラーが発生しました。', 'error');
}
function setSubmitting(isSubmitting) {
submitButton.disabled = isSubmitting;
submitButton.textContent = isSubmitting ? '登録中...' : '登録する';
}
function showMessage(text, type) {
message.textContent = text;
message.className = 'message';
if (type === 'success') {
message.classList.add('is-success');
}
if (type === 'error') {
message.classList.add('is-error');
}
}
</script>style.html
style.html には、登録中のボタン表示と、成功・失敗メッセージの色を追加します。第4回で作成した style.html に、次のCSSを追加してください。
button:disabled {
background: #94a3b8;
cursor: not-allowed;
}
.message.is-success {
background: #ecfdf5;
color: #065f46;
}
.message.is-error {
background: #fef2f2;
color: #991b1b;
}ここまでの内容をまとめた完成版は complete.zip に入れています。
7. コードの解説
送信時にブラウザの画面遷移を止める
form.addEventListener('submit', ...) は、フォームが送信されたときに実行する処理を登録します。登録ボタンは type="submit" なので、クリックすると submit イベントが発生します。キーボードでフォームを送信した場合も同じイベントが発生します。
顧客、件名、依頼内容にはHTMLの required 属性を付けています。空欄があると、ブラウザが送信を止め、submit イベントの処理は実行されません。空白だけの入力は required では空欄と判定されないため、JavaScript側で trim() した値も確認しています。
event.preventDefault() は、フォーム送信後にブラウザがページを移動・再読み込みする動作を止めます。代わりにJavaScriptからGASを呼び出すため、入力画面をそのまま表示して結果を知らせられます。入力が不足している場合はメッセージを出して return し、GASを呼び出しません。
入力値をオブジェクトにまとめる
buildRequestData() は、各入力欄の値を saveRequest(data) に渡すオブジェクトへまとめます。
| オブジェクトのキー | フォームからの取得元 | 送信する値 |
|---|---|---|
customerCode | 顧客の <select> | 選択肢の value(顧客コード) |
customerName | 選択中の <option> | data-name 属性の顧客名 |
title | 件名の <input> | 前後の空白を除いた文字列 |
description | 依頼内容の <textarea> | 前後の空白を除いた文字列 |
assignee | 担当者の <input> | 前後の空白を除いた文字列。未入力でもよい |
顧客の <option> は、画面に表示する顧客名をテキストに、顧客コードを value に持ちます。data-name から顧客名も読むため、GAS側へコードと名前の両方を渡せます。data-* はHTML要素に追加できるカスタムデータ属性で、JavaScriptでは dataset.name として読み取ります。
google.script.runは非同期で動く
google.script.run は、GAS側の saveRequest(requestData) を呼び出します。呼び出しを書いた直後に結果が返るわけではありません。結果が届くまでに時間がかかるため、呼び出しの前に setSubmitting(true) でボタンを無効にし、「登録しています」と表示します。
withSuccessHandler(handleSaveSuccess) は、GAS側の関数呼び出しが正常に完了したときに実行され、戻り値を result として受け取ります。登録できたかどうかは result.success を見て判定します。withFailureHandler(handleSaveFailure) は、GAS側で例外が発生するなど、呼び出し自体が失敗した場合の処理を登録します。google.script.run は非同期なので、戻り値をその場で変数に代入せず、ハンドラーで処理します。
GAS側で入力を確認して行を追加する
サーバー側の saveRequest(data) は、第3回で作成した処理を使います。GAS側でも顧客コード、顧客名、件名、依頼内容を確認してから 作業依頼 シートを取得します。担当者は必須項目ではありません。
シートが見つからない場合や必須項目が不足している場合、saveRequest(data) は { success: false, message: '...' } を返します。登録できる場合は Utilities.getUuid() で内部IDを作り、登録日時と更新日時に同じ Date を設定します。その後、appendRow() で作業依頼シートに1行追加します。ステータスは 未対応、受付番号は第7回で採番するため空欄です。
登録できた場合は { success: true, id: ..., message: '登録しました。' } を返します。saveRequest() は発生したエラーを catch で捕捉し、{ success: false, message: '...' } として返します。関数は結果オブジェクトを返して正常に終了するため、クライアント側では withSuccessHandler() が実行され、そこで result.success を判定します。
戻り値に応じて画面を更新する
handleSaveSuccess(result) は、まず setSubmitting(false) でボタンを再び押せる状態にします。result.success が true なら form.reset() で入力欄を初期状態に戻し、「登録しました。」と表示します。success が false の場合は、サーバーが返した result.message をエラー表示に使い、入力欄は残します。
handleSaveFailure(error) は、GAS側の関数が例外を投げた場合や、呼び出し自体に失敗した場合に実行されます。エラーの内容を表示し、ボタンを再び押せる状態に戻します。saveRequest() が返す success: false は通常の戻り値なので、こちらではなく handleSaveSuccess() 内で処理します。
showMessage() は、メッセージ本文を textContent に設定し、種類に応じて is-success または is-error クラスを追加します。style.html はこのクラスを使って成功・失敗メッセージの色を変えます。登録中のメッセージは基本の .message の見た目です。
登録中は送信ボタンを無効にするため、その画面で処理中に続けてクリックする操作を抑えられます。ただし、これはサーバー側の重複登録判定ではありません。別タブからの送信や、結果が分からないときに再送した場合の重複までは防ぎません。
8. 導入手順
- Apps Scriptエディタを開く
index.htmlとstyle.htmlを第5回の内容に更新する(Code.gsは第4回から変更ありません)- HTMLファイルとして
script.htmlを作成する - 保存する
- まだ
setupAppSheets()を実行していない場合は実行する - テストデプロイで画面を確認する
- 既存のWebアプリURLを使う場合は、デプロイを編集して新しいバージョンを選び、再デプロイする
- 顧客、件名、依頼内容、担当者を入力して登録する
最新のコードをテストデプロイで確認する
保存した最新コードを確認するには、Apps Scriptエディタの「デプロイ」からテストデプロイを開き、種類に「ウェブアプリ」を選びます。テスト用URLの末尾は /dev で、スクリプトを編集できるユーザーがアクセスできます。詳しくはGoogle公式のウェブアプリガイドを参照してください。
既存のWebアプリURLへ変更を反映する
第4回で作成した通常のWebアプリURLを使い続ける場合、コードを保存しただけでは変更後の版に切り替わりません。「デプロイの管理」で対象のデプロイを編集し、新しいバージョンを選んで再デプロイします。既存のデプロイを更新すれば、URLを変えずに新しいコードを反映できます。詳しい手順はGoogle公式のデプロイ管理ガイドを参照してください。
9. 動作確認
登録すると、ボタンが 登録中... に変わり、処理中は押せない状態になります。

登録後、画面に 登録しました。 と表示されれば成功です。入力欄は初期化され、続けて次の依頼を入力できます。

次に、作業依頼 シートを確認します。新しい行が追加され、内部ID、登録日時、更新日時、顧客コード、顧客名、件名、依頼内容、ステータス、担当者が入っていれば登録できています。

受付番号は第7回で追加するため、今回も空欄で問題ありません。
10. よくあるエラー
登録ボタンを押しても反応しない
index.html に <?!= include('script'); ?> が入っているか確認してください。
google is not defined と表示される
ローカルHTMLとして直接開いている可能性があります。GASのHTML Service上で表示してください。
作業依頼シートが見つからない
先に setupAppSheets() を実行してください。
「顧客名を入力してください」と表示される
<option> に data-name 属性が付いていない可能性があります。
<option value="C001" data-name="サンプル商事">サンプル商事</option>
のように、value とは別に data-name 属性で顧客名を設定してください。
同じデータが複数登録される
登録中にボタンが無効になるのは、同じ画面からの連続クリックを抑えるためです。別タブからの送信や、結果が分からないときの再送による重複登録までは防げません。ボタンが「登録中…」に変わるかを確認し、別の画面や再送を含む重複を防ぐ処理は別途検討してください。
11. starter.zipの案内
starter.zip には、第4回完了時点の Code.gs、index.html、style.html、README.txt を入れています。
12. complete.zipの案内
complete.zip には、Code.gs、index.html、style.html、script.html、README.txt を入れています。
13. 次回追加する機能
次回は、顧客マスタシートから有効な顧客だけを取得し、プルダウンへ表示できるようにします。



Your Message