今までの記事一覧
Googleフォーム×GASで実現する業務自動化シリーズ全記事まとめ目次
前提
この記事は、フォーム回答を保存しているスプレッドシート側のGASを前提にしています。
トリガーは以下を設定してください。
- 実行する関数:
onFormSubmit - イベントのソース:スプレッドシートから
- イベントの種類:フォーム送信時
スプレッドシート構成
スプレッドシート「集計シート」をこの図のように設定しています。
- B1セルに残数が入る
- 4行目以下にフォームから送信された注文内容が記録される
おさらい
前回は、シート上の更新ボタンを押すことで、シートに入力された残数を自動でフォームに反映するシステムを実装しました。
しかし、現場から「スマホアプリから在庫を直したとき、ボタンが動かなくて不便」と相談が。そう、スプレッドシートの図形ボタンは、スマホアプリではタップしても実行できないという致命的な仕様があります。
そこで今回は、ボタンを押さなくても「セルが書き換わった瞬間」を検知して自動実行する編集時トリガーを使って、完全スマホ対応させます。
やりたいこと
「集計シート」のB1セルの数字を書き換えたらGASが動いてフォームの残り在庫を更新、フォームの受付停止/受付再開をする。
GASのトリガーについて
ここで簡単にGASにおけるトリガーの説明をしておきます。
GASでは下の2つのトリガーを使うことができます。
-
シンプルトリガー
onEditなど決められた関数名を付けることで、自動的にシステムがトリガーであると判断して実行します。 -
インストールトリガー
決められた関数名がなく、トリガー設定画面から「トリガーを追加」することでトリガーとして使うことができるものです。
シンプルトリガーは権限が弱く、フォームを操作することができないため、今回はインストールトリガーを使用します。
今回のキモはコレ!
-
e
イベントオブジェクトと呼ばれるものです。シートの内容が編集されるという「イベント」が発生したときに、編集内容の情報がここに入ります。 -
if (sheet.getName() === "集計シート" && range.getA1Notation() === "B1")
編集されたセルがあるシートとセル番地が、ターゲットとしているもの(この場合は「集計シート」のB1セル)と同じかを判定します。
ここを変える
関数の名前を付けるときに引数にeを入れます。これにより、この関数の中で、イベントオブジェクトeを使えるようになります。
function syncOnEdit(e) {
次に、eから編集されたセルを特定します。
const range = e.range;
const sheet = range.getSheet();
編集されたセルのシートと番地が「集計シート」のB1セルかどうか判定します。
このif節の中に、今までのコードを入れていきます。
if (sheet.getName() === "集計シート" && range.getA1Notation() === "B1") {
前回はこのようにシートから取ってた残数のデータですが、今回は定数sheet_summaryを定義していないので
const currentRemaining = Number(sheet_summary.getRange("B1").getValue());
rangeを使って残数のデータを取得します。
const currentRemaining = Number(range.getValue());
range.getValue()のかわりにe.valueでもOKです。
今回は、あえてe.valueではなくrange.getValue()を使っています。
e.valueを使ったほうがシートへのアクセスが減って処理は高速になるのですが、実務ではセルの削除や複数セルの貼り付けなど、想定外の操作が行われることがあります。そのような場合、e.valueが取得できず、意図しない動作につながる可能性があります。
今回は速度よりも安定性を優先し、編集後のセルの値を確実に取得できる range.getValue() を採用しました。
関数名について
編集時のトリガーだったらonEdit(e)じゃないの?と思った方もいるかもしれませんが、上記で書いたとおり、今回はインストールトリガーを使います。onEdit はシンプルトリガーとして使われることが多く、後から見たときに分かりにくくなるため、今回はfunction syncOnEdit(e)としています。
GASコード
全体のコードはこちらです。(プルダウン型の場合)
function syncOnEdit(e) {
// イベントオブジェクト e から編集されたセルを特定する
const range = e.range;
const sheet = range.getSheet();
// 「集計シート」の、かつ(&&)「B1セル」が編集されたときだけ動かす
if (sheet.getName() === "集計シート" && range.getA1Notation() === "B1") {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const formUrl = ss.getFormUrl();
const form = FormApp.openByUrl(formUrl);
// 集計シートのB1セルに入っている最新の残数を取得
const currentRemaining = Number(range.getValue());
// 在庫が0以下の条件分岐
if (currentRemaining <= 0) {
form.setCustomClosedFormMessage("本日の注文受付は【完売】のため終了いたしました。またのご利用をお待ちしております!");
form.setAcceptingResponses(false);
return;
}
// 在庫復活時の再開処理
if (!form.isAcceptingResponses()) {
form.setAcceptingResponses(true);
}
// フォーム全体の「説明欄」を書き換える
form.setDescription("【現在の残り在庫: " + currentRemaining + " 個】");
// 特定の質問(注文数)のプルダウン選択肢を書き換える
const items = form.getItems();
for (const item of items) {
if (item.getTitle() === "注文数を選択してください") {
const listItem = item.asListItem();
const choices = [];
for (let i = 1; i <= currentRemaining; i++) {
choices.push(String(i));
}
listItem.setChoiceValues(choices);
listItem.setHelpText("※現在の残り在庫は「あと " + currentRemaining + " 個」です。");
break;
}
}
}
}
トリガー設定方法
エディタ画面左端の、目覚まし時計のアイコンをクリックし

右下に現れる「トリガーを追加」の青いボタンをクリックします。
設定画面が開くので、図のように、実行する関数に今回作った「syncOnEdit」を入れ、イベントのソースは「スプレッドシートから」、イベントの種類は「編集時」を選択します。

最後に右下の「保存」をクリックします。
これでB1セルの入力によってGASを動かすことができるようになりました。
※トリガー保存時に「承認が必要です」という画面が表示された場合は、アカウントを選択し、左下の「詳細」→「〇〇(安全ではないページ)に移動」をクリックして権限を許可してください。
動作確認
スマホから残数を変更してみましょう。
前回の記事で残数「3」になっていましたので、「5」を入力してみます。
フォームにも反映されました。
編集時トリガーを設定した場合は、前回の記事で作った更新ボタンは削除したほうがいいでしょう。
セル入力後にボタンを押すと、2重にGASが動いてしまいます。
まとめ
今回は、インストールトリガーを使い、セル入力をきっかけとしてGASが動く設定を行いました。
これでスマホからの入力でもフォームを更新することができます。
今回の成果コード
※質問のタイトル(注文数を選択してください の部分)は、ご自身のフォームの質問名に合わせて書き換えてください
プルダウン方式
function syncOnEdit(e) {
// イベントオブジェクト e から編集されたセルを特定する
const range = e.range;
const sheet = range.getSheet();
// 「集計シート」の、かつ(&&)「B1セル」が編集されたときだけ動かす
if (sheet.getName() === "集計シート" && range.getA1Notation() === "B1") {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const formUrl = ss.getFormUrl();
const form = FormApp.openByUrl(formUrl);
// 集計シートのB1セルに入っている最新の残数を取得
const currentRemaining = Number(range.getValue());
// 在庫が0以下の条件分岐
if (currentRemaining <= 0) {
form.setCustomClosedFormMessage("本日の注文受付は【完売】のため終了いたしました。またのご利用をお待ちしております!");
form.setAcceptingResponses(false);
return;
}
// 在庫復活時の再開処理
if (!form.isAcceptingResponses()) {
form.setAcceptingResponses(true);
}
// フォーム全体の「説明欄」を書き換える
form.setDescription("【現在の残り在庫: " + currentRemaining + " 個】");
// 特定の質問(注文数)のプルダウン選択肢を書き換える
const items = form.getItems();
for (const item of items) {
if (item.getTitle() === "注文数を選択してください") {
const listItem = item.asListItem();
const choices = [];
for (let i = 1; i <= currentRemaining; i++) {
choices.push(String(i));
}
listItem.setChoiceValues(choices);
listItem.setHelpText("※現在の残り在庫は「あと " + currentRemaining + " 個」です。");
break;
}
}
}
}
ラジオボタン方式
上のプルダウン方式のコードで、 //特定の質問(注文数)の「プルダウン選択肢」を書き換えるから下をこちらに差し替えてください。
//特定の質問(注文数)の「ラジオボタン選択肢」を書き換える
const items = form.getItems();
for (const item of items) {
if (item.getTitle() === "注文数を選択してください") {
// ラジオボタン(asMultipleChoiceItem)に型変換
const mcItem = item.asMultipleChoiceItem();
// 1 から currentRemaining までの「文字列の配列」を作る
const choices = [];
for (let i = 1; i <= currentRemaining; i++) {
choices.push(String(i)); // "1", "2", "3" ...と文字列で入れる
}
// ラジオボタンの選択肢を最新在庫に合わせて書き換え
mcItem.setChoiceValues(choices);
// ヘルプテキスト(説明文)を更新
mcItem.setHelpText("※現在の残り在庫は「あと " + currentRemaining + " 個」です。");
break;
}
}
}
自由記述方式
上のプルダウン方式のコードで、 //特定の質問(注文数)の「プルダウン選択肢」を書き換えるから下をこちらに差し替えてください。
//特定の質問(注文数)の「説明欄」を書き換える
const items = form.getItems();
for (const item of items) {
// 質問のタイトルが「注文数を入力してください」の項目を探す
if (item.getTitle() === "注文数を入力してください") {
// TextItem(記述式)に型変換して変数に格納
const textItem = item.asTextItem();
// ヘルプテキスト(説明欄)を更新
textItem.setHelpText("※現在の残り在庫は「あと " + currentRemaining + " 個」です。");
// 【今回の新機能】検証ルールを組み立てる
const validation = FormApp.createTextValidation()
.setHelpText(`一度に注文できるのは ${currentRemaining} 個までです。`)
.requireNumberBetween(1, currentRemaining)
.build();
// 質問欄にルールを適用(古いルールは自動で上書きされます)
textItem.setValidation(validation);
break;
}
}
}
最後に:ボタン式と編集時トリガー、どちらを選ぶ?
今回紹介した「編集時トリガー」は完全自動で便利ですが、以下のようなデメリットもあります。
- 関係ないセルを触るたびに、一瞬とはいえ裏でGASが動き続ける
- GASの実行ログに関係ない履歴が大量に残るため、後からの確認が難しくなる
ちなみに、私個人としては、自分のタイミングで確実にコントロールできる前回の「ボタン式」のほうが好みだったりします(笑)。
全自動の「編集時トリガー」か、確実性の「ボタン式」か。ご自身の環境や運用のスタイルに合わせて使い分けてくださいね。
| 方式 | PC | スマホ | 実行タイミング |
|---|---|---|---|
| 更新ボタン | ○ | × | 管理者が押したとき |
| 編集時トリガー | ○ | ○ | セル編集時に自動 |
次回は送信後のキャンセルや数量変更に対応する方法を考えていきたいと思います。




