D3- 如何透过 Google Apps Script 的 Trigger 来建立一个自动开启、调整与关闭的 Google 表单?

来到了第三天,想说我们先从一个简单但实用的 Script 开始。但先讲结论,如果你很急着用,可以直接使用这份 Add-On: Email Notification for Google Form。对於想知道怎麽做的人,让我们开始吧!

今天的目标

细数常见的 Google 产品,有一个很常用的就是 Google 表单了,有的人拿它来记帐、有的人拿它来处理报名。而在使用上,有时会听见的是——

  1. 「我要怎麽设定 Google 表单时间到了自动开启或结束」?
  2. 「要怎麽设定当报名人数到达三十人,自动关闭的表单」?
  3. 「我要如何设定客制化表单的回应?」

我们今天就以回应这三个问题来玩 GAS 罗!


Q1. 「我要怎麽设定 Google 表单时间到了自动开启或结束」?

Step 1. 将 Google 表单绑定 GAS

那我们就开始罗!首先先帮我开启一个全新的 Google 表单,并进入到 GAS 的介面。

跟第二篇提到的一样,第一次使用会需要按下「允许授权」。

Step 2. 设定想要的变数

我习惯将变数弄成一张参数表(Environment.gs),这边先设定我想要的参数,包括建立一个 form 的 object 呼叫 GAS 的 FormApp 并设定「目前绑定的表单」(如果你是从「第二张」的「进入方式一」,也就是没有绑定者,这边可以透过 FormApp.openById 来设定)。

var form = FormApp.getActiveForm();
// or var form = FormApp.openById('1234567890abcdefghijklmnopqrstuvwxyz');

再来我们来设定时间,这边我希望在中午 12 点开启,下午 5 点结束,先设定一个简单的时间。这边我写成 "2021-09-03 12:00"(YYYY-MM-DD HH:MM 的形式),实际开发时可以多考虑时区,不管是 GAS 的执行时区,以及设定时间的时区(可以参考第二篇的补充:时区设置)

var start_time = "2021-09-03 12:00";
var end_time = "2021-09-03 17:00";

Step 3. 设定表单开启与结束

我们可以在主要执行的 .gs 档案中,写上「开启」与「结束」的程序。在这边,我们透过的功能叫 setAcceptingResponses() 。并透过 Logger.log() 来通知执行的程序。

function startAcceptResponse() {
    form.setAcceptingResponses(true);
    Logger.log("Your Google Form is opening");
}

function stopAcceptResponse() {
    form.setAcceptingResponses(false);
    Logger.log("Your Google Form is closing");
}

这边我们透过无痕模式来测试其功能的结果。

Step 4. 设定 Time-Based Trigger 来开启与关闭表单

在这边,可以透过程序码设定,或是直接用 GAS 的 UI 来执行。透过 UI 的方式比较直观,就是设定你要几点启动开启(startAcceptResponse)或结束(stopAcceptResponse)。

这边也同时说明,要怎麽透过设定的参数来执行。我们透过 ScriptApp.newTrigger() 来创造。其中 newTrigger() 里面要放的是你要执行的 function 名,以及要设定是以时间为基底(timeBased()),并在指定(at(date))的时间(new Date())会触发。

function setTrigger(){
   ScriptApp.newTrigger("startAcceptResponse")
            .timeBased()
            .at((new Date(start_time)))
            .create();

    ScriptApp.newTrigger("stopAcceptResponse")
            .timeBased()
            .at((new Date(end_time)))
            .create();
};

其他设定 Time-Based Trigger 的方式 还有依据日期(atDate(year, month, day))、设定每小时都 Trigger 一次等(everyHours(n))。

这样就完成了基本的设定。如果是设定单次性的 Trigger,那其实这样就可以了,因为到其它会自动关掉。但如果是一个定期检查的呢?总不会希望它一直吃自己的 GAS Quota吧?那我们可以设定一个「一次性的清理 Trigger 的 Trigger」(有点饶口)

Step 5. 帮专案设定一个不会再启动的清理时间

这个时候我们可以设定一个时间,透过 deleteTrigger() 把所有 trigger 都删掉。

function deleteTriggers() {
    var triggers = ScriptApp.getProjectTriggers();
    for (var i = 0; i < triggers.length; i++) {
      ScriptApp.deleteTrigger(triggers[i]);
    }
}

但,那如果我们只打算清理一个 Trigger 呢?可以加入透过加入一个 if 和 getHandlerFunction() 来达到。

function deleteTriggers() {
    var triggers = ScriptApp.getProjectTriggers();
    for (var i = 0; i < triggers.length; i++) {
        if (Triggers[i].getHandlerFunction() == "stopAcceptResponse") {
          ScriptApp.deleteTrigger(triggers[i]);
      }
    }
}

写完後,最後再透过 Step 4 设定个一次性的 Trigger 来排定删除 Trigger 就完成罗!好,那这样我们完成了第一题,那如果我想限定报名人数怎麽办?


Q2. 「要怎麽设定当报名人数到达三十人,自动关闭的表单」?

Step 1. 将 Google 表单绑定 GAS(同 Q1 )

Step 2. 设定想要的变数(大部分同 Q1 )

一样在设定的参数表(Environment.gs)中设定。如果我们希望表单最多只收 30 个回应,这边就也再设置一个回应数量。如果想绑定不要重复报名,简单的做法是限定必须登入才能回应(一个帐号只能回应一次)

var max_response_count = "30";

Step 3. 设定表单开启与结束(沿用 Q1 的程序)

Step 4. 设定 Trigger 来关闭表单

我们先设定一个 Trigger 叫做 checkCount(),我们预期它每次要在每次表单送出(Submit)时检查人数。

function checkCount(){
    if (FormApp.getActiveForm().getResponses().length >= max_response_count) {
        stopAcceptResponse();
        form.setCustomClosedFormMessage("报名已经额满了");
    } else{
        Logger.log("报名成功 +1");
    }
}

如果你想让後进者(也就是关闭表单後才点开连结的人)看到客制化讯息,记得用 setCustomClosedFormMessage(),这样他们点开就会看到像这样的画面。

再来,我们透过 onFormSubmit() 来在送出时检查是不是已经超过人数了。也可以设定其他的限制,像是预算表总额金额超过一定时跳提醒等等。那在写好之後,我们一样可以透过程序码来设定,或是一样透过程序码来达成。

function setLimitTrigger(){
    ScriptApp.newTrigger("checkCount")
            .forForm(form)
            .onFormSubmit()
            .create();
}

这样就好「检查数量」罗!但目前的逻辑是「当报名超过人数时,之後的人就不能点开表单」。假设此时有一百人已经正在填表单,我们要怎麽样让「正在填表单的人」知道他没有抢到票呢?此时就是进入我们第三题了。


Q3. 「我要如何设定客制化表单的回应?」

在这边,因为目前 Google 表单并不支援动态调整,也就是说,正在填表单的人,不会更新到最新版本的回应。所以目前的方式比较阳春,是需要透过「另行通知」来执行。

所以目前 work-around 的做法会是,搜集 Email 并在「填完表单」的页面说「会寄结果信给您」,在结果通知信中显示「报名成功」或「报名失败」。因为 Email 的设定也是个不小的主题,详细的作法会在明天的文章中介绍。


好,所以今天完整的程序码如下——

var form = FormApp.getActiveForm();

function setTrigger(){
   ScriptApp.newTrigger("startAcceptResponse")
            .timeBased()
            .at((new Date(start_time)))
            .create();

    ScriptApp.newTrigger("stopAcceptResponse")
            .timeBased()
            .at((new Date(end_time)))
            .create();
};

function deleteTriggers() {
    var triggers = ScriptApp.getProjectTriggers();
    for (var i = 0; i < triggers.length; i++) {
        if (Triggers[i].getHandlerFunction() == "stopAcceptResponse") {
          ScriptApp.deleteTrigger(triggers[i]);
      }
    }
}

function checkCount(){
    if (FormApp.getActiveForm().getResponses().length >= max_response_count) {
        stopAcceptResponse();
        form.setCustomClosedFormMessage("报名已经额满了");
    } else{
        Logger.log("报名成功 +1");
    }
}

function setLimitTrigger(){
    ScriptApp.newTrigger("checkCount")
            .forForm(form)
            .onFormSubmit()
            .create();
}

如果没有程序底子/时间怎麽办?

没关系,今天讲的内容其实已经有为 Google Developer Expert 将它做成了 Add-On,所以直接使用 Email Notification for Google Form 就可以了。但对於想客制化、开发的人来说,希望透过今天的流程,有帮你更了解一些。

最後小提醒,也别设定太多 Trigger 喔,记得 Quota for Trigger20 / user / script

好,那今天就是我们的 D3,明天 D4 会继续介绍结合 Email 的使用方式,如果不想错过可以订阅按赞小铃铛(?),也欢迎留言跟我说你还想知道什麽做法/主题。我们明天见。


<<:  写给MLOps人才培育苦手 | MLOps落地指南 - 团队篇

>>:  云端定义 3

Day10-元件沟通传递(part2)

没有props还可以传资料吗 v-bind和v-on在没有props的情况下一样可以得到父层的资料。...

Day 3 - Array 阵列组合技 (2)

前言 前一篇介绍了 forEach、filter、map、reduce,算是平常我比较常使用,而且在...

Day 2:挑选 Hexo 作为工程师技术部落格

来到铁人赛第二天,今天我们来聊聊 Hexo 这个架设工具。 Hexo 是什麽? Hexo 是利用 N...

Checkbox 与 Radio 组件-金鱼都能懂的Bootstrap5网页框架开发入门

Bootstrap已是目前全球被大量网页开发者使用的一个网页UI框架了,其特色在於使用简单,开发快速...

[GAS] Genero GAS 控制台网页版 (3.20後套件提供)

在设定 GAS demos.html 时,我们能看到3.20後的版本多了一个区块 此区块即为新增的网...