2024.11.14
いまさらNode.jsを知ろう~環境構築も~
2023.02.28
開発環境・ツール【GAS】GASを触ってみる!〜Forms編〜
本ブログをお読みになってくださっている皆様、こんにちは。お久しぶりです。MTです。
皆様はいかがお過ごしでしょうか。私は恵方巻き買おうとしたらなくて長い細巻きを齧ったり、チョコ菓子を自分で作って食べるなどして時節の行事をエンジョイしていました、、、、、
気を取り直して、、本題に入りましょう。
今回も前回と同じくGASやってみました記事です!あまり複雑な事はしていないので、ちょっとプログラム触ってみたいなというコーディングの入り口や、少しでもGAS使って楽できないかなという方の助けになれば良いなと思います!
前回記事はこちらです。
今回の目標は、「スプレッドシートで管理する情報から、フォームを作成して、そのフォームの回答を集める。」
ということで、以下のような方針で進めたいと思います。
それでは始めていきましょう!
まずはスプレッドシートの用意からです。
用意するべきものは至ってシンプルで、以下の内容のシートを用意すれば問題ありません。
本当は、もっと詳細な設定などもあるのですが、ひとまずこれだけで作成していきたいと思います。
では、スプレッドシートを開きましょう。
開いた最初のシートの名前を分かりやすい名前に設定します。(ここではQuestionnaireとします。)
このQuestionnaireシートに以下の内容を記載してください。説明に関しては後ほど行います。
Title | Category | Setting_Sheet | Require |
チェックボックスグリッド | Chbox_G | Setting_ChBG | TRUE |
チェックボックス | Chbox | Setting_ChB | TRUE |
時間 | Duration | TRUE | |
日付 | Date | ||
日付+時刻 | DateTime | ||
時刻 | Time | ||
ラジオグリッド | Radio_G | Setting_RG | |
ラジオ | Radio | Setting_R | |
プルダウン | List | Setting_L | |
テキストブロック | Paragraph | ||
一行テキスト | Text | TRUE | |
スケール | Scale | Setting_S | TRUE |
今回作成するフォームは、お試しということでほぼ全ての質問形式を網羅してみたいと思い、各質問形式に対応する質問を作成していこうと思います。そのための、設定値群が上表です。これら4項目は以下のような役割を持ちます
まず、このフォームの質問形式についてざっくりご紹介させていただきます。同時に今回の設定シートに記載している内容についても概説いたします。
設定なしの項目の中にも、細かい設定ができるものはありますが、今回は必要最低限に近いものにしています。
今回の各設定シートの構成は、句点区切りで一列に列挙とします。参考にチェックボックスグリッドの設定は以下のような構成になっています。
勿論、シートやタイトルなどの名前に関しては自由なものをつけていただいて大丈夫です。特に日本語であってもGASスクリプト内で扱えるので日本語でも問題ありません。個人的な好みというかおすすめを言うのであれば、セオリー通り適当なアルファベットで扱う癖があった方が不要なトラブルを起こしにくいかな、とは思います。
長らくお待たせいたしました!ここからはGASを使っていきましょう。
GASの準備は前回もご紹介いたしましたが、簡単に言うと「ヘッダバー」→「拡張機能」→「Google App Script」で開くことができます。
では、スプレッドシートの情報を読み取る設定をしていきます。この辺りは前回とあまり変わりないので、サクッと解説しますが、ソースコードの添付で、流していきます。なお、デフォルトで最初から記載されている関数を上書きしています。
function createQuestionnaire() { // テーブルヘッダは読む必要がないので読み飛ばす const START_SETTING_ROW = 2; const DIFF_FROM_TOP = START_SETTING_ROW - 1; // spreadSheetの定義 const spreadSheet = SpreadsheetApp.getActiveSpreadsheet(); const queSetting = spreadSheet.getSheetByName('Questionnaire'); // 設定を読み込む const settings = queSetting.getRange( START_SETTING_ROW, 1, queSetting.getLastRow()-DIFF_FROM_TOP, 4) .getValues(); // 読み込んだ設定をコンソールに表示する settings.forEach((arr, index) => { console.log(index, arr) });
上記のコードでは、スプレッドシートから、先ほど記載した設定ファイルを読み込んで、一行ずつ表示する。といったことを行っています。ポイントとしては、テーブルのヘッダは、人が読んで分かりやすいように置いているだけなので、読む必要がないため、あらかじめ読み取りから外してしまっていることですね。
読み取りに関してはひとまずこれだけです。設定シートの読み取りがなくて不思議かもしれませんが、フォームの作成に強く関わってくるかなと思いますので、次項で説明したいと思います。
ここからは、今回の本題、フォームの作成に入りたいと思います。
その前に、一旦状況を整理しましょう。2のコードで取得した情報(変数: settings)についてです。
settingsは、二次元の配列で、それぞれのインデックスが示す内容(配列)は[タイトル, カテゴリー名, 設定ファイル名, TRUEまたはFALSE]ですね。このデータを利用して、フォームのアイテム(質問)を設定していきます。今回は扱うシートが複数になって多少こんがらがってしまうかも知れません、、ですので、変数の持っている値に関してなどはこのようにきちんと確認して進めましょう。
それでは、まずフォームファイル自体の作成をしていきます。
フォームファイルの作成に関しては以下のメソッドを利用します。
const form = FormApp.create('フォーム名')
左辺のformはこの後、この作成したFormを操作するために指定しておくための変数だと思っていただいて問題ないので、基本的にこのような形で定義することが多いと思います。
このコードが実行されたタイミングで、すぐに指定した名前で空っぽのフォームが作成されていると思います。もし余裕があれば、これだけ記載して確認してみてください。
上記コードにおいて、挿入箇所は大体下コードのように挿入してください。
// spreadSheetの定義 const spreadSheet = SpreadsheetApp.getActiveSpreadsheet(); const queSetting = spreadSheet.getSheetByName('Questionnaire'); // formの作成 const form = FormApp.create('テストフォーム') // 設定を読み込む
上記のように、すぐ使うわけでもないのに、設定を読み込む前のタイミングで作成している理由としては読みやすさのために、何かを定義するもの、はなるべくまとめておきたいという思いからです。別に使う前に定義さえすれば動きます。
ここからが今回の内容で、最も重要かつ複雑な箇所になるかと思います。
といってもそんな特殊なことをしているわけではないので、しっかり確認しながら進めていきましょう!
フォームに質問を追加するメソッドは、質問形式ごとに違います。さらに、このメソッドが追加するのは質問形式の箱だけです。つまり、さらにその箱の持つメソッドで選択肢などを追加する必要があります。
その対応を以下にまとめます。今回利用しているものだけに絞っていますので、もっと詳しい説明を見たい方は、公式のドキュメントを確認することをお勧めいたします。
○ CheckboxGrid Item
// フォームに質問を追加 item = form.addCheckboxGridItem(); // 質問に要素を追加: タイトル、列(小質問)、行(選択肢)、必須かどうか item.setTitle('タイトル名') .setRows('[列要素]') .setColumns('[行要素]') .setRequired('T/F');
○ Checkbox Item
// フォームに質問を追加 item = form.addCheckboxItem(); // 質問に要素を追加: タイトル、選択肢、その他を表示するか、必須かどうか item.setTitle('タイトル名') .setChoiceValues('[選択肢]') .showOtherOption('T/F') .setRequired('T/F');
○ Duration Item
// フォームに質問を追加 item = form.addDurationItem(); // 質問に要素を追加: タイトル、必須かどうか item.setTitle('タイトル名') .setRequired('T/F');
○ Date Item
// フォームに質問を追加 item = form.addDateItem(); // 質問に要素を追加: タイトル、必須かどうか item.setTitle('タイトル名') .setRequired('T/F');
○ DateTime Item
// フォームに質問を追加 item = form.addDateTimeItem(); // 質問に要素を追加: タイトル、必須かどうか item.setTitle('タイトル名') .setRequired('T/F');
○ Time Item
// フォームに質問を追加 item = form.addTimeItem(); // 質問に要素を追加: タイトル、必須かどうか item.setTitle('タイトル名') .setRequired('T/F');
○ Grid Item
// フォームに質問を追加 item = form.addGridItem(); // 質問に要素を追加: タイトル、列(小質問)、行(選択肢)、必須かどうか item.setTitle('タイトル名') .setRows('[列要素]') .setColumns('[行要素]') .setRequired('T/F');
○ MultipleChoice Item
// フォームに質問を追加 item = form.addMultipleChoiceItem(); // 質問に要素を追加: タイトル、選択肢、その他を表示するか、必須かどうか item.setTitle('タイトル名') .setChoiceValues('[選択肢]') .showOtherOption('T/F') .setRequired('T/F');
○ List Item
// フォームに質問を追加 item = form.addListItem(); // 質問に要素を追加: タイトル、選択肢、必須かどうか item.setTitle('タイトル名') .setChoiceValues('[選択肢]') .setRequired('T/F');
○ ParagraphText Item
// フォームに質問を追加 item = form.addParagraphTextItem(); // 質問に要素を追加: タイトル、必須かどうか item.setTitle('タイトル名') .setRequired('T/F');
○ Text Item
// フォームに質問を追加 item = form.addTextItem(); // 質問に要素を追加: タイトル、必須かどうか item.setTitle('タイトル名') .setRequired('T/F');
○ Scale Item
// フォームに質問を追加 item = form.addScaleItem(); // 質問に要素を追加: タイトル、範囲、必須かどうか item.setTitle('タイトル名') .setBounds(最小, 最大) .setRequired('T/F');
では、これらのメソッドを使って、フォームを設定していきましょう。
セクション2の最後、本セクションの初めの振り返りで確認した、設定シートのデータを使います。
セクション2最後のconsole.logの箇所から、以下のコードに編集します。
// 読み込んだ設定をコンソールに表示する。=>読み込んだ設定で、シートを更新する settings.forEach((arr, index) => { console.log(index); constructFormItem(spreadSheet, form, arr); });
何かよくわからない関数がいますね、、はい、自作関数です。
この処理はちょっと複雑になるので、関数に分けてしまおうと思います。詳しい処理は次に説明しますので、今は引数にスプレッドシート、フォーム、設定値の配列(本セクションはじめに提示したデータ列)を持つことだけわかっていただければと思います。
なお、console.log(index)
を残しているのは、処理の進行を可視化するためです。
独自関数constructFormItem(spreadSheet, form, arr)
の実装に進みます。
はじめにコードを紹介します。全文書くと長くなるため、ひとまず参考にCheckboxGrid Itemの設定を示します。
function constructFormItem(spreadSheet, form, setting_arr) { // 設定シートを取得 const sheet = spreadSheet.getSheetByName(setting_arr[2]); // 質問の種別で分岐 switch (setting_arr[1]) { case 'Chbox_G': if (!sheet) { // 設定が必要な場合、設定値が空ならエラーとして処理 throw new Error('CheckboxGridItem:設定用シートがありませんでした。') } // itemを作成する const item = form.addCheckboxGridItem(); // 設定の読み込み const setting = sheet.getRange(1, 1, sheet.getLastRow(), sheet.getLastColumn()).getValues(); // 空文字の除去(行列長の差による空文字) const rows = setting[0].filter(v => v); const cols = setting[1].filter(v => v); // 値のセット item.setTitle(setting_arr[0]) .setRows(rows) .setColumns(cols) .setRequired(Boolean(setting_arr[3])); break; ・・・ default: break; } }
端折りましたが結構長いですね、、それにセットしている配列に関して、setting_arr[0]
はタイトル、setting_arr[3]
はTRUEまたはFALSEである、ということがパッと見ただけでは分かりづらいです。コメントをつけるにしてもごちゃごちゃしますね。
ここでこの処理の流れを確認してみましょう。
setting_arr[2]
)から取得setting_arr[1]
)で分岐ざっと処理の流れを見ていると、4の処理のあたりからはフォームのitemに関する、また別の処理のように見えませんか?
今まとまっている処理に対し、別の処理の塊として見ることができる部分がある、ではそれを切り出して関数にしてみましょう。
すぐ上のコードの一部を関数呼び出しに書き換えます。では、引数に何を渡すべきでしょうか。
4以降の処理で扱っているのは、以下の要素ですね。
このうち、setting_arrに関しては、必要なデータはタイトルと、必須かどうかのT/Fでしたね。
では、それだけを抜き取って渡せば分かりやすくなるのではないでしょうか。
従って、以下のように分岐内容を編集したいと思います。
// 質問の種別で分岐 switch (setting_arr[1]) { case 'Chbox_G': if (!sheet) { // 設定が必要な場合、設定値が空ならエラーとして処理 throw new Error('CheckboxGridItem:設定用シートがありませんでした。') } // 設定用の関数を呼び出す setCheckboxGridItem(form.addCheckboxGridItem(), sheet, setting_arr[0], Boolean(setting_arr[3])) break;
ちょっとこの時点では分かりにくいかも知れませんが、関数定義を確認したときにわかりやすくなり、可読性が増すのではないかなと考えます。正直個人で書く分には好みですし、チームの場合はチームの規約に従うと思うので、難しいですね。
閑話休題。
では関数の定義について紹介します。例によって先にコードを紹介したいと思います。
// CheckboxGridItem: 設定シートは2行で構成、1行目はRowsに相当、2行目はColumnsに相当 function setCheckboxGridItem (item, sheet, title, require) { // 設定の読み込み const setting = sheet.getRange(1, 1, sheet.getLastRow(), sheet.getLastColumn()).getValues(); // 空文字の除去(行列長の差による空文字) const rows = setting[0].filter(v => v); const cols = setting[1].filter(v => v); // 値のセット item.setTitle(title) .setRows(rows) .setColumns(cols) .setRequired(require); }
引数はフォームのitem, 設定シート自体を受け取るsheet, setting_arrを受け取るtitle, require をセットします。その変数の用途がはっきりして、先ほどまでの状態よりは読みやすいかなと思います。
処理自体に関しては、流れで説明した通りのことを行っているのですが、一点読み込みに関して補足します。
コメントに記載していますが、必要な設定シートは簡単にするため、各行に1設定値グループの値を列挙している前提で記載しています。この入力もわかりやすくしたいのであれば、入力シートや読み出しにも工夫が必要になるかと思いますので、色々とお試しください。
それでは、サンプルであげたものを含めた2つの関数全体に関して、紹介させていただきます。
もちろん設定の取り出し方、判定などに関して全て共通しているわけではありませんので、注意深く確認していただけたらと思います。なお、備考やコメントで補足説明を加えています。
/** * 現在の構成 * spreadSheet 設定シートのあるスプレッドシート * form 作成するフォーム * setting_arr 設定オブジェクト配列 * [0]: タイトル * [1]: アイテムの種別 * [2]: 設定用シート(なしの場合空文字) * [3]: 必須項目かどうか(空文字であれば必須でない) */ function constructFormItem(spreadSheet, form, setting_arr) { const sheet = spreadSheet.getSheetByName(setting_arr[2]); switch (setting_arr[1]) { case 'Chbox_G': if (!sheet) { throw new Error('CheckboxGridItem:設定用シートがありませんでした。') } setCheckboxGridItem(form.addCheckboxGridItem(), sheet, setting_arr[0], Boolean(setting_arr[3])) break; case 'Chbox': if (!sheet) { throw new Error('CheckboxItem:設定用シートがありませんでした。') } setCheckboxItem(form.addCheckboxItem(), sheet, setting_arr[0], Boolean(setting_arr[3])) break; case 'Duration': form.addDurationItem() .setTitle(setting_arr[0]) .setRequired(Boolean(setting_arr[3])); break; case 'Date': form.addDateItem() .setTitle(setting_arr[0]) .setRequired(Boolean(setting_arr[3])); break; case 'DateTime': form.addDateTimeItem() .setTitle(setting_arr[0]) .setRequired(Boolean(setting_arr[3])); break; case 'Time': form.addTimeItem() .setTitle(setting_arr[0]) .setRequired(Boolean(setting_arr[3])); break; case 'Radio_G': if (!sheet) { throw new Error('GridItem:設定用シートがありませんでした。') } setGridItem(form.addGridItem(), sheet, setting_arr[0], Boolean(setting_arr[3])) break; case 'Radio': if (!sheet) { throw new Error('MultipleChoiceItem:設定用シートがありませんでした。') } setMultipleChoiceItem(form.addMultipleChoiceItem(), sheet, setting_arr[0], Boolean(setting_arr[3])) break; case 'List': if (!sheet) { throw new Error('MultipleChiceItem:設定用シートがありませんでした。') } setListItem(form.addListItem(), sheet, setting_arr[0], Boolean(setting_arr[3])) break; case 'Paragraph': form.addParagraphTextItem() .setTitle(setting_arr[0]) .setRequired(Boolean(setting_arr[3])); break; case 'Text': form.addTextItem() .setTitle(setting_arr[0]) .setRequired(Boolean(setting_arr[3])); break; case 'Scale': if (!sheet) { throw new Error('ScaleItem:設定用シートがありませんでした。') } setScaleItem(form.addScaleItem(), sheet, setting_arr[0], Boolean(setting_arr[3])) break; default: break; } }
// CheckboxGridItem: 設定シートは2行で構成、1行目はRowsに相当、2行目はColumnsに相当 function setCheckboxGridItem (item, sheet, title, require) { // 設定の読み込み const setting = sheet.getRange(1, 1, sheet.getLastRow(), sheet.getLastColumn()).getValues(); // 空文字の除去(行列長の差による空文字) const rows = setting[0].filter(v => v); const cols = setting[1].filter(v => v); // 値のセット item.setTitle(title) .setRows(rows) .setColumns(cols) .setRequired(require); } // CheckboxItem: 設定シートは1,または2行で構成、1行目に設定値、2行目に値があれば「その他」フラグがたつ function setCheckboxItem (item, sheet, title, require) { // 設定の読み込み const setting = sheet.getRange(1, 1, sheet.getLastRow(), sheet.getLastColumn()).getValues(); // 配列の空文字除去(想定外エラー回避) const rows = setting[0].filter(v => v); // 値のセット item.setTitle(title) .setChoiceValues(rows) .showOtherOption(sheet.getLastRow() != 1) .setRequired(require); } // GridItem: 設定シートは2行で構成、1行目はRowsに相当、2行目はColumnsに相当 function setGridItem (item, sheet, title, require) { // 設定の読み込み const setting = sheet.getRange(1, 1, sheet.getLastRow(), sheet.getLastColumn()).getValues(); // 空文字の除去(行列長の差による空文字) const rows = setting[0].filter(v => v); const cols = setting[1].filter(v => v); // 値のセット item.setTitle(title) .setRows(rows) .setColumns(cols) .setRequired(require); } // MultipleChoiceItem: 設定シートは1,または2行で構成、1行目に設定値、2行目に値があれば「その他」フラグがたつ function setMultipleChoiceItem (item, sheet, title, require) { // 設定の読み込み const setting = sheet.getRange(1, 1, sheet.getLastRow(), sheet.getLastColumn()).getValues(); // 配列の空文字除去(想定外エラー回避) const rows = setting[0].filter(v => v); // 値のセット item.setTitle(title) .setChoiceValues(rows) .showOtherOption(sheet.getLastRow() != 1) .setRequired(require); } // ListItem: 設定シートは1行で構成、1行目に設定値 function setListItem (item, sheet, title, require) { // 設定の読み込み const setting = sheet.getRange(1, 1, sheet.getLastRow(), sheet.getLastColumn()).getValues(); // 値のセット item.setTitle(title) .setChoiceValues(setting[0]) .setRequired(require); } // ScaleItem: 設定シートは1行で構成、A1に下限値、B1に上限値(3~10まで) function setScaleItem (item, sheet, title, require) { // 設定の読み込み const min = sheet.getRange('A1').getValue(); const max = sheet.getRange('B1').getValue(); // 値のセット item.setTitle(title) .setBounds(min, max) .setRequired(require); }
駆け足な説明で基本コードの紹介になってしまって、申し訳ございません。しかし、全部解説するとくどくなりすぎるかなと思いますし、紹介させていただいたCheckboxGrid Itemの設定をベースに読んでいただければ、読み解けるのではないかなと思います。
ここまででフォームを完成させることが出来ました!お疲れ様でした!
この状態で、新しく作成したフォームのページにアクセスして回答すると、フォームのデータ収集が可能です。(Google Workspaceでアクセスするほか、作成したフォームへのリンクを作成するform.getPublishedUrl()
の返り値をconsole.log()
で表示することでアクセスできます。)
しかし、収集したデータだけ別の管理になるのは少し違和感がありますね、、
Google Formsには回答のコピーをスプレッドシートに送る機能があります。その設定を次のセクションで紹介いたします
わざわざセクションを分けましたが、そんなに大変なことはしません。
form.setDestination(type, id)
という関数を呼び出すだけで、設定することが可能です。
typeはFormApp.DestinationType.SPREADSHEET
のみがデフォルトで設定可能で、idには操作しているスプレッドシートのidを設定します。getId()
関数を用いれば簡単に設定できます。
では、createQuestionnaire()関数の一番最後に以下の内容を記載します。
function createQuestionnaire() { ・・・ settings.forEach((arr, index) => { console.log(index) constructFormItem(spreadSheet, form, arr); }); //Form回答コピー設定 form.setDestination(FormApp.DestinationType.SPREADSHEET, spreadSheet.getId()); }
これで、回答の記録がコピーされて、スプレッドシートにも流入するようになります。
この流入先シート名をコードで変えることは出来ないのですが、このシートを対象とするスクリプトを組むことで、収集したデータを加工していくことができますね。
以上で、今回の本題である「スプレッドシートで管理する情報から、フォームを作成して、そのフォームの回答を集める。」に関して完了です!お疲れ様でした!!
ここからフォーム作成のテンプレとすることもできますし、収集データを表に加工することもできますし、活用の仕方は工夫次第でどんどん広がっていくでしょう!ここでは初歩的なことしかしていないので、今すぐは難しいかもしれませんが、是非考えてみてください!!
今回の記事は以上になります。ちょっとややこしい記事になり、ソースを見てください!の箇所も多くなってしまってすみません。
今回作ったものは、「特定のこれ」といったものでなく、「汎用的なパーツ」のような機能ですので、別ファイルに関数だけ切り出して、また他のツールを使うときに組み込んでみる、といった使い方もできるのではないかなと思います。
次回もこのような形で、GASのやってみた記事を上げられたらと思っておりますので、よければ目を通していただけると嬉しいです!!
それでは、最後までお付き合いいただき、ありがとうございました!MTでした。
【記事への感想募集中!】
記事への感想・ご意見がありましたら、ぜひフォームからご投稿ください!【テクノデジタルではエンジニア/デザイナーを積極採用中です!】
下記項目に1つでも当てはまる方は是非、詳細ページへ!Qangaroo(カンガルー)
【テクノデジタルのインフラサービス】
当社では、多数のサービスの開発実績を活かし、
アプリケーションのパフォーマンスを最大限に引き出すインフラ設計・構築を行います。
AWSなどへのクラウド移行、既存インフラの監視・運用保守も承りますので、ぜひご相談ください。
詳細は下記ページをご覧ください。
最近の記事
タグ検索