gas

Google Apps Script(GAS)&Googleフォーム&スプレッドシートでよく使うコード

Googleフォームの回答をスプレッドシートに収集して、Google Apps Script(GAS)でゴニョゴニョすることがある。とっても便利。

なにはともあれまずは公式サイト
https://developers.google.com/apps-script/
https://developers.google.com/apps-script/reference/spreadsheet/

ググるのもQiitaを見るのもいいけど、エンジニアたるもの

  • 一次情報源にあたり情報の真偽を自分で確かめること
  • マニュアル・ヘルプ等の使い方に慣れて自分で調べられるようになること

がとても大切だ。
優秀なエンジニアはむやみやたらにググったりはしないものだ(たぶん)。

逆引き

フォーム送信時の回答を取得

トリガーを設定する。
実行:submitForm
イベント:「スプレッドシートから」「フォーム送信時」

フォームを送信すると、submitFormメソッドが実行されてe.rangeで取得できる。

eについてはEvent Objectsを読んでおく。

function submitForm(e) {
  var range = e.range; //=> Rangeオブジェクトで取得
  Logger.log(range.getRow());
  var array = e.values;
  Logger.log(array); //=> ['2015/05/04 15:00', 'Jane', 'Doe']
  var json = e.namedValues;
  Logger.log(json); //=> { 'Timestamp': ['2015/05/04 15:00'], 'First Name': ['Jane'], 'Last Name': ['Doe'] }
}

see also
- Event Objects
- Class Logger

フォーム送信のテストコード

動作を確認するたびにフォームを開いて各項目を入力してフォーム送信なんていちいちやってられない。

トリガーに呼び出されるファンクションは回答をメイン・ファンクションに渡すだけの単純なものにする。
メイン・ファンクションにRangeオブジェクトを渡すというテストコードを準備する。

function submitForm(e) {
  var range = e.range; //=> Rangeオブジェクトで取得
  mainFunction(range);
}

function mainFunction(range) {
  // ....
}

testMainFunction() {
  var range = SpreadsheetApp.getActiveSheet().getRange("A30:Q30");
  mainFunction(range);
}

see also
- getRange(a1Notation)

シートの最終行を取得(=フォーム送信時の回答が追加された行)

回答をEvent Objects(参照:フォーム送信時の回答を取得)からRangeで受け取っているならRangeオブジェクトから行番号を得ればよいが、配列として扱っていたり、フォーム送信時以外に最終行番号を得たい場合などに使う。

var sheet = SpreadsheetApp.getActiveSheet();
var lastRow = sheet.getLastRow();
var lastColumn = sheet.getLastColumn();
var lastCell = sheet.getRange(lastRow, 1, 1, lastColumn);
Logger.log(lastCell.getValue());

see also
- getLastRow()

フォーム送信者(回答者)のメールアドレスを取得

フォーム送信者(回答者)がGoogleにログインしていなかったらダメだろうね。

var recipient = Session.getActiveUser().getEmail(); //=> grgrjnjn@example.com

see also
- Class Session
- getEmail()

スプレッドシートを開く

ID指定でスプレッドシートを開く

スプレッドシートIDは、スプレッドシートのURLが https://docs.google.com/spreadsheets/d/abc1234567/edit なら「abc1234567」となる。

var ss = SpreadsheetApp.openById("abc1234567");
Logger.log(ss.getName());

URL指定でスプレッドシートを開く

URLでedit移行を含めた形 https://docs.google.com/spreadsheets/d/abc1234567/edit#gid=1234567890 を指定してもgid移行は無視される。gidまで指定してシートを開けばgetActiveSheet()メソッドで指定のシートを得られるだろうという期待は裏切られる。

var ss = SpreadsheetApp.openByUrl(
    'https://docs.google.com/spreadsheets/d/abc1234567/edit');
Logger.log(ss.getName());

see also
- openById(id)
- openByUrl(url)

シートの名前からシートを開く

シートの名前はユーザが変更できるのでシートの保護をうまく使うなどしてシート名が変更されないようにする必要がある。

var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("フォームの回答 1");
if (sheet != null) {
  Logger.log(sheet.getIndex());
}

see also
- getSheetByName(name)

シートのすべてのセルを取得

var data = sheet.getDataRange().getValues();

ユーザがシートをいじっても影響を受けないように列番号を取得

フォームの回答はシートに保存されるが、このシートは列のタイトルを変更してもいいし、列の順序を入れ替えたり関係のない列を挿入してもよい。Googleフォームかっこいい!だから、ユーザーはシートを扱いやすいように適宜編集してしまう前提でGASを作りこまなければならない。つらい...。

テクニックとして、列の特定に「名前付き範囲」を使う。ユーザは滅多に使わないだろうといういい加減な前提に立っている。危うい...。

列のタイトルのセルを右クリック「名前付き範囲を定義」から設定する。
この例では「timestamp」という名前を付けた。getRangeByNameメソッドがあるのは、Spreadsheetクラスであって、Sheetクラスではないところに注意。

var timestampColumn = SpreadsheetApp.getActiveSpreadsheet().getRangeByName('timestamp').getColumn();

see also
- getRangeByName(name)
- getColumn()

列が追加されても対応できるフォーム回答の取り方

フォームの回答(入力内容)が記録されるシートの回答が記録される列よりも左側に列を挿入しすると(フォームは挿入された列ではなくもとの列に適切に回答を記録してくれるが)回答の項目が記録される列番号がズレるので問題となる。

ズレても適切に列番号を取得する方法は前項に書いた。

それでもまだ問題として残るのは、A列に挿入した場合だ。回答を取得する定番e.rangeは、回答が記録される列と列の間に挿入された回答に関係ない列は一緒に取得するが、回答の記録される最も左の列よりさらに左の列は取得しない。右も同様。

これに対処するためにe.rangeを諦め、フォーム送信時にシートの最終列をA列から明示的に取得するという手法をとる。

この項はrange.getRow()で書き直したほうがよいだろうなぁ。

  var sheet = SpreadsheetApp.getActiveSheet();
  var lastRow = sheet.getLastRow();
  var lastColumn = sheet.getLastColumn();
  var range = sheet.getRange(lastRow, 1, 1, lastColumn );
  Logger.log(range.getValues());

see also
- getLastRow()
- getLastColumn()

フォームの日付入力欄が空なら今日を入れる