【0からGASを学ぶ】GASを用いたGoogleフォーム内の項目設定チートシート

# はじめに
本シリーズでは、GASの始め方や便利な使い方、ビジネス活用まで幅広く解説します。シリーズをひと通り読んでいただければ、あなたもきっとGASマスターになれるはずです。

# シリーズの対象者
* そもそもGASってなんだか**わからない**方
* GASを学びたいけど**何から始めればいいか**わからない方
* GASはわかり始めたけど、**もっと活用ができないか**と模索している方
* とにかく**Googleが好き！** という方

# 前回記事
https://qiita.com/gas-suke/items/da2616f71ad52926d277

# GoogleフォームをGASで更新する
では早速始めていきましょう。【0からGASを学ぶ】シリーズの第19回は「GASを用いたGoogleフォーム内の項目設定チートシート」です。今回は、Googleフォームで**提供している各種フォーム項目をGASからいじくって**みます。あの項目はどうやっていじるんだ？と**疑問に思ったときはこのチートシートを活用**してください。Googleフォームは単なる質問受付に使えるだけではなく、**フォーム自体をテストとして扱い**、自動採点やフィードバックを行うこともできる**非常に強力なツール**です。とはいえ、本チートシートでは**主たる用途である質問受付に特化**してお伝えします。

## 事前準備
### Googleフォームを作成
今回のいじくり先のフォームを作成していきましょう。項目は空で構いません。GAS側でどんどん項目を追加していきます。

### GASエディタを開く
準備したGoogleフォームから**コンテナバインド型でGASプログラム**を記述していきましょう。Googleフォームからコンテナバインド型のGASを開く場合は、<a href="https://qiita.com/gas-suke/items/cf4a809d54464a43f3ac#gas%E3%82%A8%E3%83%87%E3%82%A3%E3%82%BF%E3%82%92%E9%96%8B%E3%81%8F" target="_blank">こちら</a>を参考にしてください。では、どんどんいきますよ、ついてきてください。

## チートシート開始
チートシートに記載の内容は、以下を併せて確認すると理解が深まります。

https://developers.google.com/apps-script/reference/forms/form?hl=ja

### フォームそのもの
#### 設定
Googleフォームの設定項目は非常に多いです。「回答者の**メールアドレスを収集**するか」や「**クイズ形式**とするか（いわゆるテストモード）」、「回答時の**確認メッセージ**」「受付を**クローズ**とするか」など、**多岐にわたり**ます。チートシートでは**代表的な以下の5点**を設定する方法をお伝えします。

| メソッド名 | 説明 |
|:--|:--|
| setTitle(title) | フォームのタイトルを設定します。 |
| setDescription(description) | フォームの説明を設定します。 |
| setConfirmationMessage(message) | フォームの確認メッセージを設定します。 |
| setCustomClosedFormMessage(message) | フォームが回答を受け付けていない場合に表示するメッセージを設定します。 |
| setAcceptingResponses(enabled) | フォームが現在回答を受け付けているかどうかを設定します。 |

```javascript:フォームに対する設定
const form = FormApp.getActiveForm();
form.setTitle('Qiita019_項目の新規作成')
    .setDescription('このフォームはQiita019で項目の新規作成を説明するためのものです。')
    .setConfirmationMessage('Qiita019の回答を受け付けました。')
    .setCustomClosedFormMessage('Qiita019の回答は現在受け付けておりません。')
    .setAcceptingResponses(true);
```

#### 項目の一括削除
```javascript:フォーム内の項目を一括削除
const form = FormApp.getActiveForm();
form.getItems().forEach(function(item){
  form.deleteItem(item);
});
```

#### 項目の一部削除
何をもとに**項目を判断するかは任意で決めてよい**ですが、**一般的には項目タイトル**がやりやすいと思います。
```javascript:フォーム内の項目を一部削除
const form = FormApp.getActiveForm();
form.getItems().forEach(function(item){
  if (item.getTitle()=='削除したい項目名') {
    form.deleteItem(item);
  }
});
```

### 項目をいじくる
#### 全項目共通
GoogleフォームにGASから項目を**追加、更新**する場合は、前回もお伝えした通り、`addXXXXItem()`とするか`asXXXXItem()`とし、項目を生成します。その際に、全項目に共通して使われるのが以下の**3つのメソッド**となります。
| メソッド名 | 説明 |
|:--|:--|
| setTitle(title) | 項目のタイトルを設定します。 |
| setHelpText(text)	 | 項目の説明文を設定します。 |
| setRequired(enabled) | 項目が回答必須かどうかを設定します。<br/>※本項目はレイアウト要素（セクションヘッダーや画像項目等）には不要です。 |

#### 記述式
```javascript:記述式
const form = FormApp.getActiveForm();
let textValidation_number = FormApp.createTextValidation()
                                   .requireNumber()
                                   .setHelpText('数字で入力してください。')
                                   .build();

form.addTextItem().setTitle('記述式の質問項目')
                  .setHelpText('記述式の質問項目に対する説明文')
                  .setValidation(textValidation_number)
                  .setRequired(true);
```
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3575702/f026bdc0-9b1c-a2e7-80d5-1e113b2e39bf.png" width="600">

:::note info
記述式の場合には、記載した**内容に対する検証**を行うことができます。上記の場合は`requireNumber`を用いたため数値であるかどうかを判断しています。その他には以下のようなものがあります。
| メソッド名 | 説明 |
|:--|:--|
| requireNumber() | テキスト アイテムは数値にする必要があります。 |
| requireNumberBetween(start, end) | テキスト アイテムは、開始から終了までの範囲内の数値（両端を含む）にする必要があります。 |
| requireNumberGreaterThan(number) | テキスト アイテムは、指定した値より大きい数値である必要があります。 |
| requireTextIsEmail() | テキスト アイテムはメールアドレスである必要があります。 |
| requireTextMatchesPattern(pattern) | パターン（正規表現）に一致するレスポンスが必要です。 |

※全メソッドはこちらをチェック！
https://developers.google.com/apps-script/reference/forms/text-validation-builder?hl=ja
:::

#### 段落
```javascript:段落
const form = FormApp.getActiveForm();
let paragraphtextValidation_length = FormApp.createParagraphTextValidation()
                                            .requireTextLengthGreaterThanOrEqualTo(100)
                                            .setHelpText('少なくとも100文字以上入力してください。')
                                            .build();

form.addParagraphTextItem().setTitle('段落の質問項目')
                           .setHelpText('段落の質問項目に対する説明文')
                           .setValidation(paragraphtextValidation_length)
                           .setRequired(true);
```
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3575702/2d419c50-6ae7-9bfb-afcd-a0ffb5c460fa.png" width="600">

:::note info
段落の場合には、記載した**内容に対する検証**を行うことができます。上記の場合は`requireTextLengthGreaterThanOrEqualTo`を用いたため最小入力文字数を制限しました。その他には以下のようなものがあります。
| メソッド名 | 説明 |
|:--|:--|
| requireTextContainsPattern(pattern) | パターンを含むレスポンスが必要です。 |
| requireTextDoesNotContainPattern(pattern) | パターンが含まれていないレスポンスが必要です。 |
| requireTextDoesNotMatchPattern(pattern) | パターンに一致しないレスポンスが必要です。 |
| requireTextLengthGreaterThanOrEqualTo(number) | レスポンスの長さはこの値以上である必要があります。 |
| requireTextLengthLessThanOrEqualTo(number) | レスポンスの長さは値より短くする必要があります。 |
| requireTextMatchesPattern(pattern) | パターンに一致するレスポンスが必要です。 |
:::

#### ラジオボタン
```javascript:ラジオボタン
const form = FormApp.getActiveForm();
form.addMultipleChoiceItem().setTitle('ラジオボタンの質問項目')
                            .setHelpText('ラジオボタンの質問項目に対する説明文')
                            .setChoiceValues(['選択肢１', '選択肢２', '選択肢３'])
                            .showOtherOption(true)
                            .setRequired(true);
```
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3575702/66577451-958d-8214-1503-546c3236f5f2.png" width="600">

:::note info
ラジオボタンの場合には、`showOtherOption`を用いることで **「その他」の選択肢を追加**することができます。
また、**String配列による選択肢追加の`setChoiceValues`** とは別に**Choice（単一の選択肢をサポート）配列による`setChoices`** もあります。これの場合、**選択した場合に次セクションを分岐させる**ことも設定次第で実現できるため非常に便利ですが、**GASでの組み込みの場合少々複雑**になりますので、また別の機会に。
:::

#### チェックボックス
```javascript:チェックボックス
const form = FormApp.getActiveForm();
let checkboxValidation_exactly = FormApp.createCheckboxValidation()
                                        .requireSelectExactly(2)
                                        .setHelpText('２つ選択してください。')
                                        .build();

form.addCheckboxItem().setTitle('チェックボックスの質問項目')
                      .setHelpText('チェックボックスの質問項目に対する説明文')
                      .setChoiceValues(['選択肢１', '選択肢２', '選択肢３'])
                      .showOtherOption(true)
                      .setValidation(checkboxValidation_exactly)
                      .setRequired(true);
```
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3575702/9f03e525-b940-72c4-ae5b-759c00d86445.png" width="600">

:::note info
チェックボックスの場合には、選択した**内容に対する検証**を行うことができます。上記の場合は`requireSelectExactly`を用いたため選択することのできる個数を制限しました。その他には以下のようなものがあります。
| メソッド名 | 説明 |
|:--|:--|
| requireSelectAtLeast(number) | この数以上の選択肢を選択する必要があります。 |
| requireSelectAtMost(number) | 選択する最大数の選択肢が必要です。 |
| requireSelectExactly(number) | この数の選択肢を厳密に選択する必要があります。 |
:::

#### プルダウン
```javascript:プルダウン
const form = FormApp.getActiveForm();
form.addListItem().setTitle('プルダウンの質問項目')
                  .setHelpText('プルダウンの質問項目に対する説明文')
                  .setChoiceValues(['選択肢１', '選択肢２', '選択肢３'])
                  .setRequired(true);
```
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3575702/54e81ce5-b769-e215-6331-ebd5a8baf4d8.png" width="600">

:::note info
プルダウンの場合には、ラジオボタンと同様に**Choice（単一の選択肢をサポート）配列による`setChoices`** を用いることで**選択した場合に次セクションを分岐させる**ことも設定次第で実現できます。
:::

#### 均等目盛
```javascript:均等目盛
const form = FormApp.getActiveForm();
form.addScaleItem().setTitle('均等目盛の質問項目')
                   .setHelpText('均等目盛の質問項目に対する説明文')
                   .setBounds(0, 10)
                   .setLabels('最悪', '最高')
                   .setRequired(true);
```
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3575702/b6dea449-f3b2-d95a-1ebc-28897f1c6884.png" width="600">

#### 選択式（グリッド）
```javascript:選択式（グリッド）
const form = FormApp.getActiveForm();
let gridValidation = FormApp.createGridValidation()
                            .requireLimitOneResponsePerColumn()
                            .build();

form.addGridItem().setTitle('選択式（グリッド）の質問項目')
                  .setHelpText('選択式（グリッド）の質問項目に対する説明文')
                  .setRows(['１行目', '２行目', '３行目'])
                  .setColumns(['１列目', '２列目', '３列目'])
                  .setValidation(gridValidation)
                  .setRequired(true);
```
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3575702/d575585d-b381-7a17-a284-45b6d0b970ce.png" width="600">

:::note info
選択式（グリッド）の場合には、**選択内容の制限**を行うことができます。なお、
* 各**行**で**1つの回答**を必須にする：`setRequired`
* 1**列**につき**1つの回答**に制限：`setValidation`（`requireLimitOneResponsePerColumn`）

となりますのでご注意ください。
:::

#### チェックボックス（グリッド）
```javascript:チェックボックス（グリッド）
const form = FormApp.getActiveForm();
let checkboxGridValidation = FormApp.createCheckboxGridValidation()
                                    .requireLimitOneResponsePerColumn()
                                    .build();

form.addCheckboxGridItem().setTitle('チェックボックス（グリッド）の質問項目')
                          .setHelpText('チェックボックス（グリッド）の質問項目に対する説明文')
                          .setRows(['１行目', '２行目', '３行目'])
                          .setColumns(['１列目', '２列目', '３列目'])
                          .setValidation(checkboxGridValidation)
                          .setRequired(true);
```
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3575702/2ef3ecc2-0af5-56f0-6f0e-598e27afba89.png" width="600">

:::note info
チェックボックス（グリッド）の場合も**選択式（グリッド）と同様の注意**が必要です。
:::

#### 日付
日付は**若干の癖**があります。というか、まずは`DateItem`と`DateTimeItem`がありますが、これは`DateTimeItem`を使いましょう。これでないと、**時刻を含めた日付項目を実現できません。**
また、GoogleのAPI仕様に**明確に誤りがあります**のでご留意ください。

:::note alert
API仕様においては当該クラスに設定できるメソッドに`setIncludesYear`があります。<a href="https://developers.google.com/apps-script/reference/forms/date-time-item?hl=ja#setIncludesYear(Boolean)" target="_blank">API仕様</a>には
> 日付項目に年の設定を含めるかどうかを設定します。新しい日付アイテムのデフォルトは true です。

と記載がありますが、これは**明確に誤り**です。その**誤りを以下で確認**しましょう。
:::

##### 年も時刻も含める
```javascript:日付１
const form = FormApp.getActiveForm();
form.addDateTimeItem().setTitle('日付の質問項目')
                      .setHelpText('日付の質問項目に対する説明文')
                      .setRequired(true);
```
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3575702/f7bec6fa-30d1-b9da-52ca-6f12b1b93168.png" width="600">


##### 年のみを含める
```javascript:日付２
const form = FormApp.getActiveForm();
form.addDateTimeItem().setTitle('日付の質問項目')
                      .setHelpText('日付の質問項目に対する説明文')
                      .setIncludesYear(true)
                      .setRequired(true);
```
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3575702/50f26854-84b7-4ede-ed49-e178efdf06b9.png" width="600">

##### 年も時刻も含めない
```javascript:日付３
const form = FormApp.getActiveForm();
form.addDateTimeItem().setTitle('日付の質問項目')
                      .setHelpText('日付の質問項目に対する説明文')
                      .setIncludesYear(false)
                      .setRequired(true);
```
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3575702/c46b764c-a337-098f-70e4-62cafa12dde7.png" width="600">

:::note info
このように`setIncludesYear`を**呼び出すかどうかで項目の状態が変化**しますので、デフォルトが`true`というわけではなく、
* 年も時刻も含める：`setIncludesYear`を**呼び出さない**
* 年のみを含める：`setIncludesYear(true)`で呼び出す
* 年も時刻も含めない：`setIncludesYear(false)`で呼び出す

が**正しい利用方法**となります。ご留意ください。
:::

#### 時刻
時刻も**日付と同様に若干の癖**があります。ですが、こちらは追加したい項目に応じて、クラスを分けるだけです。

##### 時刻に対する質問項目
```javascript:時刻１
const form = FormApp.getActiveForm();
form.addTimeItem().setTitle('時刻の質問項目_時刻')
                  .setHelpText('時刻の質問項目に対する説明文')
                  .setRequired(true);
```
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3575702/24a9e957-4e69-6f79-a80f-fbb15d0475ab.png" width="600">


##### 経過時間に対する質問項目
```javascript:時刻２
const form = FormApp.getActiveForm();
form.addDurationItem().setTitle('時刻の質問項目_経過時間')
                      .setHelpText('期間の質問項目に対する説明文')
                      .setRequired(true);
```
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3575702/c55657b4-b5a3-73dd-1197-ee21585a8ef8.png" width="600">

#### レイアウト要素
##### タイトルと説明
```javascript:タイトルと説明
const form = FormApp.getActiveForm();
form.addSectionHeaderItem().setTitle('タイトルと説明を追加')
                           .setHelpText('これは単なるレイアウト要素');
```
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3575702/7ea38165-8147-27e6-aa8f-f2917612eb93.png" width="600">

##### セクション
```javascript:セクション
const form = FormApp.getActiveForm();
form.addPageBreakItem().setTitle('２ページ目')
                       .setHelpText('レイアウト要素ですが、分岐とかも設定が可能')
```
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3575702/bba62bf1-cc87-2363-6a04-7ec85ad478b2.png" width="600">

:::note info
「タイトルと説明」に非常によく似ていますが、**次にどのセクションへ行くか**、ラジオボタンやプルダウン項目と併せて**どのセクションから来るかなどを設定**することができます。
:::

##### 画像
```javascript:画像
const form = FormApp.getActiveForm();
form.addImageItem().setTitle('画像の表示項目')
                   .setHelpText('画像の表示項目に対する説明文')
                   .setImage(DriveApp.getFileById('XXXXXXXXXXXXXXXXXX').getBlob())
                   .setAlignment(FormApp.Alignment.CENTER)
                   .setWidth(300);
```
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3575702/09d7a934-5cbc-4c2b-4f40-99ec063ad463.png" width="600">

:::note info
`setImage`には**Blob形式で設定する必要があり**ますので、例えばGoogleドライブ上の画像であれば、`getFileById`で取得したオブジェクトに対して`getBlob`を用いて設定しましょう。
:::

##### 動画
```javascript:動画
const form = FormApp.getActiveForm();
form.addVideoItem().setTitle('動画の表示項目')
                   .setHelpText('動画の表示項目に対する説明文')
                   .setVideoUrl('VyzqHFdzBKg')
                   .setAlignment(FormApp.Alignment.LEFT)
                   .setWidth(300);
```
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3575702/b932a709-e8b4-2156-9f00-97fc764494b9.png" width="600">

:::note info
`setVideoUrl`には**YouTube上の動画のみ許容**されています。
この時、YouTube上の**動画ID**、**URL**、**ショートURL**どれでも構いません。
```javascript:例えば
.setVideoUrl('https://www.youtube.com/watch?v=VyzqHFdzBKg')
.setVideoUrl('https://youtu.be/VyzqHFdzBKg')
```
:::

### できないこと
基本的には**GUIでできることはGASから行うことができ**ます。しかしながら、一部は**実現不可な箇所**がありますので、それを認識しましょう。
#### 各項目の装飾要素をいじる
Googleフォームでは例えば、質問項目タイトルを太字にしたり、説明文の大事な箇所に下線引いたりと、装飾要素も細かに設定ができますが、これをGAS側で行うことはできません。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3575702/1a0b6383-3e23-34ea-64ca-99377588914d.png" width="600">

#### ファイルのアップロード
Googleフォームでは「ファイルのアップロード」を質問項目として定義し、例えば**申し込みに必要な添付書類を受け付ける**ことが可能です。しかしながら、これをGAS側から追加することはできません。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3575702/cf3b32b0-c313-7104-6fde-68e0c084c5d7.png" width="600">

#### しかしながら
いずれ**GASがパワーアップ**し、これらさえ**できるようになる日が来る**と私は信じています。むしろ**確信**しています。というのも、ファイルのアップロードに関しては、**全くできないわけではありません。**
```javascript:ファイルのアップロードを複製
form.getItems().forEach(function(item){
  if (item.getTitle()=='ファイルのアップロード') {
    let uploadItem = item.duplicate();
    uploadItem.setTitle('ファイルのアップロード複製');
  }
});
```
とすると、`item.duplicate()`でエラーとなりますが、フォーム内の**ファイルのアップロード項目の複製**されています。例えば、これが
```javascript:アップロード
item.asFileUploadItem().duplicate();
```
とかのメソッドが**GASに拡充されれば、きっと実現できるはず**です！

# おわりに
お疲れ様でした。
第19回は「GASを用いたGoogleフォーム内の項目設定チートシート」ということで、Googleフォーム内の**項目をGASからいじるための呼び出し方やテクニック**を一括でお伝えしました。あとは、これをどう**組み合わせるか**です。第18回に行ったように**回答を受け付けるのに併せてプルダウン内の項目を更新**するのもいいでしょうし、**各業務の実態ややりたいことに併せて取捨選択**していきましょう。皆さんの業務改善に**少しでも役に立てることを願って**います！引き続き、GASを楽しんでいきましょう！！
記事を読んで、「良いな」や「今後に期待できる！」と感じて頂けたら**いいね**や**フォロー**、**コメント**いただけると幸いです。それではまた次回をお楽しみに！

:::note info
ブログではより細かく紹介していますので、ぜひ<a href="https://gas-4u.com/" target="_blank">こちらもアクセス！！</a>
:::