アクトインディ開発者ブログ

子供とお出かけ情報「いこーよ」を運営する、アクトインディ株式会社の開発者ブログです

Google Apps Script でGoogleフォームをいじる

こんばんは。情シス(PCおじさん)の kadota です。

これは actindi Advent Calendar 2019、2日目の記事です。 12/2中に記事を出す予定でしたが、ぐずぐずしていたらすっかりこんな時間に…。こっそり記事をアップします。

adventar.org

Googleフォームを使って参加受付したい(かつ自動で締め切りたい)という話

Googleフォーム、アンケートを回収するときなどに便利ですよね。 このフォームを使ってあるイベントの参加受付をしたい、かつ自動で締め切りたいという話を社員から聞き、調べてみました。
※ネット上にはこの手の記事は色々とあるのですが、自分の場合はこうやった、という記録の意味も含めて残します。

単純に1つのイベントの参加を締め切る場合

イベント日時が単一のこの日、と決まっていて参加者の申し込みを受付けるだけの場合は、フォームにたまった回答の数を数えるだけです。LIMIT_COUNT に上限値を入れておき、

var form = FormApp.getActiveForm();

if (form.getResponses().length >= LIMIT_COUNT) {
  //ここで何らかの処理をする
}

と条件分岐します。

複数イベントの参加を締め切る場合

しかし今回は開催日が複数ありました。
こんな感じです。

f:id:ka-dot-a:20191203020721p:plain

それぞれのイベントで応募を受け付けるので、全ての上限に達してはじめてフォームを締め切ることができます。
そこで、各開催の受付数を調べて、上限に達したイベントについてはダイアログを出して注意しようと考えました(手抜き)。ダイアログはこんな感じのものを用意すれば出せる…

Browser.msgBox("(締め切り状況のテキスト)", Browser.Buttons.OK);

…はずでしたが、フォームには Browser.msgBox が使えないということがわかりました。困った。

仕方なく、応募が上限に達したイベントを選択肢から消していく、という力技のやり方でいくことにしました。

まずはカウントするところから。フォームで受け取った全ての回答を集計します。
ANS_WORD1 ANS_WORD2 には選択肢を識別する特徴的な文字列を入れてやります。

var LIMIT_COUNT = 3;
var ANS_WORD1 = '【1回目】';
var ANS_WORD2 = '【2回目】';
var form = FormApp.getActiveForm();

function _getAnswerCount() {
  var cnt1 = 0;
  var cnt2 = 0;

  form.getResponses().forEach(function(resp){
    var items = resp.getItemResponses();
    items.forEach(function(item){
      var answer = item.getResponse();
      if (answer.indexOf(ANS_WORD1) != -1) {
          cnt1++;
      }
      else if (answer.indexOf(ANS_WORD2) != -1) {
          cnt2++;
      }
    });
  });
  var r = {ans1: cnt1, ans2: cnt2};
  return r;  
}  

次に、上限に達したイベントを選択肢から消しこむ部分です。 _modifyChoices('特徴的な文字列') と呼んで選択肢からその項目を外します。

function _modifyChoices(targetVal){
  var TARGET_ITEM = '希望日時';
  var CLOSE_TEXT = '※各回ともに締め切りました';

  form.getItems().forEach(function(item){
    if (item.getTitle() == TARGET_ITEM) {
      var choices = [];
      
      item.asMultipleChoiceItem().getChoices().forEach(function(choice){
        var choiceVal = choice.getValue();
        if (choiceVal.indexOf(targetVal) != -1) {
          Logger.log('DELETED: ' + choiceVal); // 選択肢を消す(何もしない)
        }
        else {
          choices.push(choiceVal); // 選択肢を残す
        }
      });
      
      // 選択肢が1つもない場合は全て締め切った旨を表示する
      if (choices.length < 1) {
        choices.push(CLOSE_TEXT);
      }
      
      //選択肢を再生成する
      item.asMultipleChoiceItem().setChoiceValues(choices);
    }
  });
}

これらを繋ぎこむ部分。フォーム送信時の値を取得し、その選択肢の上限をチェックします。

function _choiceBuilder(itemResponses) {
  var value = itemResponses[0].getResponse(); //1問目の回答
  var r = _getAnswerCount();

  if (value.indexOf(ANS_WORD1) != -1 && r.ans1 >= LIMIT_COUNT) {
    _modifyChoices(ANS_WORD1);
  }
  else if (value.indexOf(ANS_WORD2) != -1 && r.ans2 >= LIMIT_COUNT) {
    _modifyChoices(ANS_WORD2);
  }
}

function onFormSubmit(e) {
  _choiceBuilder(e.response.getItemResponses());
}

この中の function onFormSubmit() は、onEdit() のようなシンプルトリガーと違って、明示的にトリガーを指定してやる必要があります。
イベントのソースを「フォームから」、イベントの種類を「フォーム送信時」にします。

f:id:ka-dot-a:20191203020815p:plain

トリガー設定時に権限を確認して「フォームの表示と管理」を許可するようにします。

f:id:ka-dot-a:20191203133408p:plain

トリガー設定完了。トリガー関係のUIが以前よりもモダンな感じに変わっていました。

f:id:ka-dot-a:20191203020855p:plain

フォームを送信すると、

f:id:ka-dot-a:20191203021000p:plain

定員に達したイベントから順に選択肢から消えていき…

f:id:ka-dot-a:20191203020943p:plain

全てのイベントが定員に達すると選択肢が無くなってしまうので、

f:id:ka-dot-a:20191203021052p:plain

代わりに締め切った旨のテキストを置きました。

f:id:ka-dot-a:20191203021036p:plain

ただここはそれよりもGoogleフォームの標準の「回答の受け付けを終了する」機能を呼び出したいところです。 そこで function _modifyChoices() の一部を下記のように変えて、標準の回答締め切り状態にしました。

      // 選択肢が1つもない場合は全て締め切った旨を表示する
      if (choices.length < 1) {
        //choices.push(CLOSE_TEXT);
        form.setAcceptingResponses(false);
      }

f:id:ka-dot-a:20191203021121p:plain

まとめ

今回の例は、選択肢が2つの場合に限ったものでろくに抽象化できてないですが、ある程度抽象化しておけば他にも使い回すのが楽になりそうです。
標準機能では対応できないはずのものも、工夫することで実現でき、手間を減らせるのはうれしいですね。

アクトインディでは、PCや自動化・効率化に興味のある情シス アルバイトも募集中です!

採用情報 | アクトインディ株式会社