見出し画像

「GAS×Googleフォーム」をコピーして利用する

▍何の話?

Google Apps Script(GAS)を使うと、Googleフォームで回答があったときに自動処理を行えます(例:メールやチャットへの転送、カレンダーへの予定追加、データベースへのデータ追加)。

この自動処理については様々な方が書かれているので、他の記事に譲らせていただきます。たとえば次の記事などが分かりやすいです。

GASのスクリプトが付いたフォームについて、1つのフォームを使い続ける場合は問題ありません。
一方、1つのフォームをコピーして利用する場合、単純にコピーするとスクリプトの自動処理がうまくいきません。これはたとえば、イベントアンケートのひな形があって、イベントの開催に合わせてコピーして利用する場合などです。

▍フォームコピーの問題と対処

自動処理がうまくいかない理由は、コピー時にトリガーが削除されるためです(この動きはフォームだけでなく、ドキュメントでもスプレッドシートでも、スクリプトが付いたファイルに共通です)。

スクリプト付きフォームをコピーすると、トリガーが削除される

トリガーは、イベント(きっかけ)と関数(処理内容)の設定を持ち、フォーム回答時の自動処理に必要となるものです。ですので、コピー後にトリガーを設定し直す必要があります。

▍GASでの実装(考え方)

GASでフォームコピー・トリガー追加を行う場合、少し工夫が求められます。やり方は様々でしょうが、以降はうちの会社の例で紹介します。

準備

まず、フォームとスクリプトを分離します。スクリプトには、次の2点を記述します。

  • フォーム回答時の自動処理内容

  • フォームコピーの処理(後述)

ということで、コピー処理もこのスクリプトで行います。なお、ここではまだトリガーは登場しません。

フォームとスクリプトを分離

フォームコピーの処理

フォームコピーの処理内容は次のとおりです。

  • テンプレートのフォームをコピーする

  • コピーした後に、コピー先のフォームにトリガーを設定する

なお、トリガーの指定先はフォームなのですが、トリガー自体はスクリプト側が持ちます。

フォームをコピーし、トリガーを設定する
フォームを2つコピーした場合、トリガーも2つ設定する

コピー数に注意

GASでは、1つのスクリプトで設定できるトリガーは20までという制約があります(次のリンクの「Triggers」)。

なので、紹介した方法は、コピー数が多い場合には向きません
うちでは1週間に1つ新しいフォームをコピーしているので、普通に使うと20週でパンクしてしまいます。そこで、最新の5程度で回るような運用にして、古いフォームおよびトリガーを削除しています。

ちなみに、フォームとスクリプトが分離している場合、フォームを削除してもトリガーは残ります。20の上限が危ぶまれる際には、削除済みフォームのトリガーも削除ください。

フォームを削除してもトリガーは残る

▍GASでの実装(コード)

ScriptAppクラスに次のようなトリガー操作のメソッドがあるので、これらを利用します。

  • 作成:newTrigger

  • 削除:deleteTrigger

  • 一括取得:getProjectTriggers

詳しく知りたい方は公式ドキュメントも参照ください。

フォームコピー&トリガー追加のスクリプト

// フォームコピーの処理
function copyForm() {
  // ① コピー元のファイル(フォーム)を指定
  const sourceFile = DriveApp.getFileById("コピー元のフォームのファイルID");

  // ② コピーし、コピー先のフォームのファイルIDをdestFileに格納
  const destFile = sourceFile.makeCopy("コピー先のフォーム名");

  // ③ コピー先のフォームを指定
  const form = FormApp.openById(destFile.getId());

  // ④ コピー先のトリガーを設定
  ScriptApp.newTrigger("フォーム回答時の自動処理を記述した関数の名前")
    .forForm(form)
    .onFormSubmit()
    .create()  
}

// ⑤ フォーム回答時の自動処理
function respondForm(e) {
  // フォーム回答時の自動処理内容;
}

②の補足:
「"コピー先のフォーム名"」を省略した場合、「〜のコピー」というフォーム名になります。

④の補足:
この例の場合、「フォーム回答時の自動処理を記述した関数の名前」は⑤の「respondForm」になります。
この例でのトリガーのイベントは「フォーム送信時」です。他のイベントを指定する場合、次をドキュメントを参照ください。

トリガー削除のスクリプト

削除済みフォームのトリガーを削除するスクリプトです。新たにトリガーをつける前に、この関数を実行しています。

/**
*削除済みフォームのトリガーを削除
*/
function DeleteTrigger() {
  // トリガーを一括取得
  ScriptApp.getProjectTriggers()
    // フォームに絞り込み
    .filter(trigger => trigger.getEventType() == ScriptApp.EventType.ON_FORM_SUBMIT)
    .forEach(trigger => {
      try {
        // フォームのソースIDのファイルを取得
        DriveApp.getFileById(trigger.getTriggerSourceId());
      } catch (e) {
        // 取得に失敗(エラーが発生)したらトリガーを削除
        ScriptApp.deleteTrigger(trigger);
      }
    });
}

やっていることはコメントのとおりですが、ここでは filter、forEach とアロー関数を使っているので、JavaScriptに不慣れだと面を食らうかもしれません。公式の deleteTrigger のサンプルは for で書かれているので、必要に応じてご確認ください。

おまけ

フォームコピー&トリガー追加について、うちで実際に使っているスクリプトです(関数名は変えています)。前述のコードとの違いは以下の4点です。

  • 関数化している

  • フォームを所定のフォルダにコピーしている

  • フォームのタイトルを変えている

  • 回答URLを短縮形で取得している

/**
*アンケートフォームをコピー
*@param {string} fileId - アンケートフォーマットのファイルID
*@param {string} folderId - フォームのコピー先フォルダID
*@param {string} eventId - イベントID
*@param {string} dateString - 開催日(yyyyMMdd)
*@return {string} フォーム回答用のURL
*/
function xxxCopyForm(fileId, folderId, eventId, dateString) {
  // IDをもとに、コピー元のフォームのファイルを指定
  const sourceFile = DriveApp.getFileById(fileId);
  // IDをもとに、フォームのコピー先フォルダを指定
  const destFolder = DriveApp.getFolderById(folderId);

  // フォームのファイルを所定のフォルダにコピー
  const destFile = sourceFile.makeCopy(`${dateString}_${eventId}`, destFolder);

  // コピー先のフォームを指定
  const form = FormApp.openById(destFile.getId());
  // フォームのタイトルを設定
  form.setTitle("ご参加者アンケート");
  // フォームにトリガーを追加
  ScriptApp.newTrigger("xxxRespondForm")
    .forForm(form)
    .onFormSubmit()
    .create();
  // フォームの回答URLを返却
  return form.shortenFormUrl(form.getPublishedUrl());
}

▍おわりに

鍵となる注意事項は次の2点ですので、是非抑えておいていただければと。

  • スクリプト付きのファイルをコピーするとトリガーが削除される

  • 1つのスクリプトのトリガー上限は20

スクリプト付きのフォームをコピーするケースが稀なのか、情報が少なく、うちでは少しハマりました。これから対応する方のご参考になれば嬉しいです。


私たちのIT活用のメモが次のマガジンにあります。よろしければ覗いてみてください!

いいなと思ったら応援しよう!