很多时候我们会需要搜集些不同的资料。像是 Marketing 在做大规模但针对不同组织的调查问卷。如果只是三份、五份的问卷要做客制化、统整算还好;但如果是一百份、甚至上千份时,总不能一个个复制了吧。此时就会遇到个问题——
因为篇幅关系,这边会拆成三篇来写,第一篇与第二篇回应 Q1;第三篇回应 Q2。今天这篇是针对 Q1 的第二篇,昨天我们讲了怎麽样复制与简单客制 Google 表单。而今天来到了第十二天,我们来到目前最进阶的操作,用 GAS 完成超·客制表单。一样先讲结论,如果你很急着用,可以直接使用这份 Add-On: Form Publisher,功能非常强大。自己写的好处是,如果你一天突然要做些高度客制化,那此篇会有帮助。这篇的定位比较像是字典、工具,在你需要用的时候可以来参照。那就让我们开始吧!
复制表单有两种方式。一种是比较简单的「复制范本」,简单来说就是针对一个表单复制,然後再改期中的元素。另一种是「从零制作」,这种就比较复杂,因为会是透过 GAS 完整制作表单,会需要比较熟悉 GAS。我们昨天讲完方法一,今天则着重在方法二。
如果是比较复杂的表单与设定,举例来说,有些人需要第一题,有些人第五题要用填空,那要怎麽处理?这个时候可以用 GAS 来制作每份表单。虽然这样客制化程度很高,但相对制作时间会比前一次更久。果然是充满取舍的人生。
也给大家看一下这次预计生成的参数们,左边绿色是我们输入的参数,右边橘色是我们预计输出的参数。
补充:校稿时有朋友问说那个「勾勾」怎麽做,多录一支影片给大家看
设定步骤跟之前一一样,从 Google Sheet 中进入 GAS。
昨天我们有示范如何操作既存的表单,今天我们来看看怎麽生出新的表单。方式极其简单,就是用 FormApp.create()
,并在括号中输入表单名称即可。
function createNewForm(){
let new_form = FormApp.create('New Form');
}
但,这样创造有个小问题,就是创造的位置会在根目录,也就是一开始打开 Google Drive 的位置。那要怎麽移动?目前是需要比较阳春的透过写 DriveApp
的 moveTo(folder)
才行。也就是以下示范的程序码,改编自 JLMosher 的回应。
function moveFile(fileId, destinationFolderId) {
let destinationFolder = DriveApp.getFolderById(destinationFolderId);
DriveApp.getFileById(fileId).moveTo(destinationFolder);
}
所以原本的表单,使用上就变成了——
function createNewForm(){
let new_form = FormApp.create('New Form');
let new_form_id = new_form.getId()
let destinationFolderId = "your_folder_id_here"
moveFile(new_form_id, destinationFolderId)
}
那这边是简易生成一张表单的方式,接着我们要对每一张表单进行细节的操作,开始罗!
FormApp
来生出表单的问题我们要如何在 GAS 内生出问题们?我做了一个简单方法对照表。
那实际上怎麽用呢,这边先给大家看完整的程序码,接着一个个说明。我们以一份要约 onsite interview 的表单为例。
function addNameText(form){
let text_name = form.addTextItem().setTitle('Name').setRequired(true);
return text_name
}
function addLunchList(form){
let list_item = form.addListItem();
list_item.setTitle('Option for the Lunch')
.setChoices([
list_item.createChoice('Meat'),
list_item.createChoice('Vegetarian')
]);
return list_item
}
function addTrafficChoices(form){
let multipleChoice_item = form.addMultipleChoiceItem();
multipleChoice_item.setTitle('How do you come to our office?')
.setChoices([
multipleChoice_item.createChoice('Train'),
multipleChoice_item.createChoice('Bus'),
multipleChoice_item.createChoice('Drive'),
multipleChoice_item.createChoice('Walk')
])
.showOtherOption(true);
return multipleChoice_item
}
function addInterestGridWithValidation(form){
let grid_item = form.addGridItem();
grid_item.setTitle('Rate your interests')
.setRows(['SDE', 'Test Engineer', 'Project Manager'])
.setColumns([5, 4, 3,2,1])
.setHelpText("It won't affect your scores in interviewing.");
let gridValidation = FormApp.createGridValidation()
.setHelpText("Select one item per column.")
.requireLimitOneResponsePerColumn()
.build();
grid_item.setValidation(gridValidation);
return grid_item
}
function addSkillCheckbox(form){
let checkbox_item = form.addCheckboxItem();
checkbox_item.setTitle('What are your technical skillsets')
.setChoices([
checkbox_item.createChoice('Python'),
checkbox_item.createChoice('JavaScript'),
checkbox_item.createChoice('HTML5'),
checkbox_item.createChoice('CSS3')
])
.showOtherOption(true);
return checkbox_item
}
function addAvailableDateTime(form){
let date_time_item = form.addDateTimeItem();
date_time_item.setTitle('When is your availability?');
return date_time_item
}
function addSuggestionParagraphText(form){
let paragraph_text_item = form.addParagraphTextItem();
paragraph_text_item.setTitle('Any question or suggestion?');
return paragraph_text_item
}
function addRateScale(form){
let scale_item = form.addScaleItem();
scale_item.setTitle('Rate this form')
.setBounds(1, 5);
return scale_item
}
function writeForm(curr_form){
curr_form.setTitle('D12 Form').setDescription('Description of form \nTest for new line');
let text_name = addNameText(curr_form);
let list_item = addLunchList(curr_form);
let multipleChoice_item = addTrafficChoices(curr_form);
let grid_item = addInterestGridWithValidation(curr_form);
let checkbox_item = addSkillCheckbox(curr_form);
let date_time_item = addAvailableDateTime(curr_form);
let paragraph_text_item = addSuggestionParagraphText(curr_form);
let scale_item = addRateScale(curr_form);
return curr_form
}
好,那我们一个个来讲。顺序依照最上面图的顺序。
这边应该算好理解,针对 form
本身用 setTitle()
设定标题,也透过 setDescription()
设定标题下的叙述。
form.setTitle('D12 Form').setDescription('Description of form \n Test for new line');
眼尖的朋友应该有看到我有加入一个 '\n'
在 Description,这是「换行符号」,也就是在叙述段落如 description
时,可以透过加上这符号进行换行。换句话说,如果输入
// 会出现连续的 123
.setDescription('123')
123
// 会出现分成三行的 1, 2, 3
.setDescription('1\n2\n3')
1
2
3
补充的是,这边有三个功能是可以加上的,分别是收到回应的确认讯息、是否允许编辑与是否接受重复回应。
form.setConfirmationMessage('Thanks for responding!')
.setAllowResponseEdits(true)
.setAcceptingResponses(false);
对应的回应关系图如下——
这张表是用中文版 Google Form 在新增问题时的顺序,也是我们接下来列点介绍的顺序。
这边很简单地用了 setTextItem()
作为了设定问题的方式,并且针对这个新增的问题用 setTitle()
来给予叙述,并且用 setRequired
来设定必填。
let text_name = form.addTextItem().setTitle('Name').setRequired(true);
而对应的段落也是用 .addParagraphTextItem()
即可。
let paragraph_text_item = form.addParagraphTextItem();
paragraph_text_item.setTitle('Any question or suggestion?');
但如果有时候我们想要加上一些限制,像是至少输入 100 字,那要怎麽做?这时就要用到 createParagraphTextValidation
来执行。范例程序码如下——
let paragraphtextValidation = FormApp.createParagraphTextValidation()
.setHelpText(“Answer must be more than 100 characters.”)
.requireTextLengthGreatherThan(100);
paragraph_text_item.setValidation(paragraphtextValidation);
而其总共有六种模式可以设定,分别是...
需要含有
以下 Pattern requireTextContainsPattern(pattern)
:通常是开头、结尾需要是特定格式。 e.g. email 的信箱位址。不得含有
以下 Pattern requireTextDoesNotContainPattern(pattern)
需要吻合
以下 Pattern requireTextMatchesPattern(pattern)
:通常是字句中需要含有特定关键字、模式。 e.g. 含有三码邮递区号数字後接上文字不得吻合
以下 Pattern requireTextDoesNotMatchPattern(pattern)
长度大於或等於
数字requireTextLengthGreaterThanOrEqualTo(number)
:e.g. 地址文字中不得含有数字(需用国字中文之类)长度小於或等於
数字 requireTextLengthLessThanOrEqualTo(number)
上面的功能中所提到的 Pattern
,其实就是 Regex(Regular Expression 正规表示式)。这边给一个使用的范例。
let paragraphtextValidation = FormApp.createParagraphTextValidation()
.requireTextContainsPattern('[a-zA-Z]')
paragraph_text_item.setValidation(paragraphtextValidation);
上面所写的这个范例就是,检查输入的内容只能是英文大写 [A-Z]
或小写 [a-z]
。更详细 Regex 可以到 regexone 和 learn regex 学,很详尽。
下面这段程序码,主要是先用 addMultipleChoiceItem()
创造一个 object,并接着用 setChoices()
和 createChoice)_
来创造选项们,最後设定 showOtherOption()
来让填答人可以自行输入「其他」。
let multipleChoice_item = form.addMultipleChoiceItem();
multipleChoice_item.setTitle('How do you come to our office?')
.setChoices([
multipleChoice_item.createChoice('Train'),
multipleChoice_item.createChoice('Bus'),
multipleChoice_item.createChoice('Drive'),
multipleChoice_item.createChoice('Walk')
])
.showOtherOption(true);
方式跟设定选择题几乎一样,只差在是用 addCheckboxItem()
来建造。
let checkbox_item = form.addCheckboxItem();
checkbox_item.setTitle('What are your technical skillsets')
.setChoices([
checkbox_item.createChoice('Python'),
checkbox_item.createChoice('JavaScript'),
checkbox_item.createChoice('HTML5'),
checkbox_item.createChoice('CSS3')
])
.showOtherOption(true);
额外有 CheckboxValidationBuilder
可以建造验证程序,方式包括
requireSelectAtLeast()
requireSelectAtMost()
requireSelectExactly()
提供官方范例给大家参考~
var checkBoxValidation = FormApp.createCheckboxValidation()
.setHelpText(“Select two condiments.”)
.requireSelectExactly(2)
.build();
checkBoxItem.setValidation(checkBoxValidation);
透过 addListItem()
来建置即可,比较没看到特别好玩的部分
let list_item = form.addListItem();
list_item.setTitle('Option for the Lunch')
.setChoices([
list_item.createChoice('Meat'),
list_item.createChoice('Vegetarian')
]);
透过 addScaleItem()
来建置。基本上是输入数值。那身为中文使用者,会很想问说,那怎麽样输入中文?这时就要透过 setLabels('Bad', 'Good')
的方式。
let scale_item = form.addScaleItem();
scale_item.setTitle('Rate this form')
.setBounds(1, 5)
.setBounds('Bad', 'Good');
藉由 addGridItem
来新增。
let grid_item = form.addGridItem();
grid_item.setTitle('Rate your interests')
.setRows(['SDE', 'Test Engineer', 'Project Manager'])
.setColumns([5, 4, 3,2,1])
.setHelpText("It won't affect your scores in interviewing.");
且一样有 GridValidationBuilder 可以建立。但方式只有一种,也就是限制每一直栏都只能有一个被填入:requireLimitOneResponsePerColumn()
。程序码如下。
let gridValidation = FormApp.createGridValidation()
.setHelpText("Select one item per column.")
.requireLimitOneResponsePerColumn()
.build();
grid_item.setValidation(gridValidation);
那会想问,如果我想设定的是每个横的行,都必须要填入一个选项呢?就单纯用 .setRequired(true)
即可做到了。
那至於「核取方块格」呢?因为方式都一样,所以可以单纯地把 addGridItem
换成 addCheckboxGridItem
。两者的 validation method 也都都是只有一种,但核取方块格需要将 GridValidationBuilder 换成 CheckboxGridValidationBuilder()
就是。
日期与时间也相对单纯,用 addDateTimeItem()
即可。
let date_time_item = form.addDateTimeItem();
date_time_item.setTitle('When is your availability?');
好,但我们的重点是客制化表单,要怎麽样将原本的做表格变客制化呢?这边先用个简单的方式。
function writeForm(){
let data = readData();
let new_forms_id_arr = [];
for(row_data of data){
let form_name = row_data[0];
let form_description = row_data[1];
let curr_form = FormApp.create(form_name);
let curr_form_id = curr_form.getId()
new_forms_id_arr.push([curr_form_id]);
moveFile(curr_form_id, target_folder_ID)
curr_form.setTitle(form_name).setDescription(form_description);
let question_function_list = [addNameText,
addLunchList,
addTrafficChoices,
addInterestGridWithValidation,
addSkillCheckbox,
addAvailableDateTime,
addSuggestionParagraphText,
]
for(let i = 2; i< row_data.length; i++){
if(row_data[i] == true){
question_function_list[i-2](curr_form);
}
}
// add general item
addRateScale(curr_form);
}
writeData(new_forms_id_arr)
}
里头要用到的功能上方都有写,可以直接复制喔!
其实已经偷偷写在上面的 Code 里面了,请看 Step 3 最後 writeData()
的部分。
完整执行画面——
回对我们的任务表,确认 Ben 表单中是没有「下拉选单」午餐编号的。
任务完成!
好,我们总算完成了(落泪),虽然昨日 D11 介绍的第一种方式比较简单,但实际上想弹性运用,我们会需要很多 D12 的内容。但,如果我们今天真的生了 100 份表单,那要怎麽样统一回应?总不能慢慢搜集吧。这时候就会需要看我们的 D13 了。
不知不觉就写了一整天...Orz,希望大家喜欢。一样提醒,用FormApp.create()
来创造表单是有 Quota 限制——每天不超过 250 份。如果还有问题,透过留言之外,也可以到 Facebook Group,想开很久这次铁人赛才真的开起来哈哈哈,欢迎来当 Founding Member。如果不想错过可以订阅按赞小铃铛(?),也欢迎留言跟我说你还想知道什麽做法/主题。我们明天见。
>>: [Angular] Day12. Template variables
在昨天将 index 成功推上 Github 後,今天该让他有点东西了。 打开 VSCODE 後 按...
随着资讯技术普及与推陈布新,基础设施及服务(IaaS)、平台即服务(PaaS)、软件即服务(Saa...
前面10天测试完大概的API功能後, 今天本来要开始建立API方法, 但用Anaconda建置API...
今天的 TextField 和明天的 FormControl 都是在介绍跟表单有关的介面和元件,而...
Youtube 频道:https://www.youtube.com/c/kaochenlong ...