GoogleAppsScriptを使ってみよう(おまけ編)

#はじめに
[GoogleAppsScriptを使ってみよう(前編)](https://qiita.com/omomukumamani_piroshi/items/beeb51d8c28369167cd9)
[GoogleAppsScriptを使ってみよう(後編)]
(https://qiita.com/omomukumamani_piroshi/items/585c4478474b8d8c571c)
今回の記事は上記の二つの記事を読んでいることを前提として書いています。
一部内容が伝わりにくい場合や話の繋がりがわからない場合には上記二つの記事を読んでいただけると理解がしやすくなるかもしれません。

業務の効率化や自動化への注目が集まる中、度々行うちょっとした手作業を少しでも減らしたい人やプログラミングに興味があるけどちょっとやるにはハードルが高いなと感じている方を対象として、今日はGoogleAppsScriptを例にコーディングに関するお話をしていきたいと思います。

今回はおまけ編ということで、前編、後編からさらにステップアップして自分の生活の中で使用している(た)「スプレッドシートとカレンダーの連携」について紹介していこうと思います。

#なぜSpreadSheetとカレンダーを連携するのか
<img width="1291" alt="スクリーンショット 2019-12-20 15.33.33.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/423369/3fe9c679-8a68-6647-cef1-e6a57141b4df.png">
画像を見てください。
わたしはライブに行くのが好きで色々なところに行っていたのですが、ライブへ参加するためにはチケットが必要であったりします。
e+、ぴあ、TIGETなどのチケットの取り扱いを行う会社がまちまちであったり、そもそも興行主が一括で当日手売りを行なっていたり、さらには発券方法はファミリーマート、ローソン、セブンイレブンで端末に番号を入力したり店員が勝手に発券してくれたりします。
さすがに自分の記憶力だけで全ての入金日やその金額、店舗、そして実際にライブが開催される日程や時刻とその場所を覚えておくことは困難でした。
このためチケットに関する入金状況や発券状況をスプレッドシートで管理していました。PC上から見るぶんにはスプレッドシートでも全く問題ないのですが、スマホでスプレッドシートを見ようとすると結構辛かったりした経験などないでしょうか?
そしてライブ当日にスマホからチケットの入金状況などほぼ見る必要がなく場所と時間が分かれば良いことがほとんどであるためスケジュールはカレンダーで見ることができればそれで十分だったのです。
幸いなことに日付や時間などはスプレッドシートで管理していたためこれをGoogleAppsScriptでカレンダーに転送できれば要件は満たせそうでした。
ということで早速準備をしていきましょう。

#スプレッドシートの準備
なぜスプレッドシートとカレンダーを連携するのかについてはすでに説明をしましたが、何かしらの数値やデータとスケジュールを一つのシートで管理したいという要件を満たすため、スプレッドシートは下記のような構成にしてあります。
このうちカレンダーの連携で必要な項目については、「開始時刻」「終了時刻」「名前」「場所」「詳細」「登録状況」になります。

| 開始時刻 | 終了時刻 | 名前 | any | any | any | any | any | any | any | 場所 | 詳細 | 登録状況 |
|:---------|:---------|:---------|:---------|:---------|:---------|:---------|:---------|:---------|:---------|:---------|:---------|:---------|
||||||||||||||
1. 開始時刻・・・スケジュールが開始される時刻。「2019/12/23 10:00:00」の形式で入力します。
2. 終了時刻・・・スケジュールが終了する時刻。入力の形式は開始時刻と同様。
3. 名前・・・カレンダーのタイトルとなります。内容のわかる文字列であればなんでも大丈夫です。
4. 場所・・・文字列でもGoogleMapなどを参照するURLでも良いです。
5. 詳細・・・スケジュールの内容を具体的にするものを入力します。文字列でもURLでも良いです。
6. 登録状況・・・データの入力規則を使用して「登録する」「登録済み」を選択できるようにします。後ほど説明しますが、カレンダーへ登録する必要は今のところないが前後の予定を把握するために入力しておくような場合は空欄に。カレンダーへ登録を行う場合には「登録する」を。すでにカレンダーへ登録されたものは「登録済み」となります。

なお、anyと書かれたところはスプレッドシートで管理するために用意したため、単にカレンダーと連携をするのであれば一つも必要ないですし、これより増える場合には必要に応じて追加しても良いです。

シート名は「シート1」から「管理」へ変更しておきましょう。

#CalenderID
Googleカレンダーを開きます。
カレンダーの左側に下の画像のような表示があります。
<img width="226" alt="スクリーンショット 2019-12-23 10.31.54.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/423369/d71757cc-d9ac-9fa9-4526-f4156801ff6b.png">

この中からスプレッドシートと連携を行いたいカレンダーにマウスのカーソルを合わせるとその右側に縦に3つの点が表示されます
<img width="267" alt="スクリーンショット 2019-12-23 10.35.32.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/423369/81b330a5-ae82-052f-6172-d2b0ebacd066.png">

これをクリックすると下の画像のメニューが表示されます。
この中から設定と共有を選択します。
<img width="201" alt="スクリーンショット 2019-12-23 10.36.52.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/423369/23bfdf9c-c17d-b012-8fb7-cd9bbab28259.png">

スクロールして下の方へ移動すると、「カレンダーの統合」という項目があります。
ここにカレンダーIDが表示されています。
<img width="198" alt="スクリーンショット 2019-12-23 10.38.03.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/423369/6cd39cb8-d144-c3ea-2074-97a136f0c0ba.png">

このIDを使用してカレンダーとの連携を行います。

#スクリプト
##の前に
今回紹介するスクリプトでは、「const」「配列」「for文」が新しい要素として登場します。
一つずつ覚えていきましょう。
##const
「const」という単語が出てきましたが、GoogleAppsScriptでは「定数」を表します。「var」で始まるものは「変数」と呼びました。変数は後から変更が可能でしたが定数は宣言の時の代入(初期化)のみ受け付けます。以降の変更は不可となるためスクリプト中で変更されないが直接使用されるとスクリプトの内容がわからなくなるような数値を使用する際に定義します。

```Javascript
const domain_string = '@gmail.com'
Logger.log('hina' + domain_string);
Logger.log('mei' + domain_string);
Logger.log('aoi' + domain_string);
```
上記の例ではemailのアドレスのドメイン名をconstとして定義しています。
gmailを使用する際にドメイン名の@gmail.comはほとんど変更しない(する必要がない)ためconstとして定義しておけば誤って変更してしまったりすることが無くなります。
スクリプト中で使用する意味のある数値や文字列はconstで定義するようにするとスクリプトが読みやすくなります。

##配列
「配列」は連続した変数を想像するとわかりやすいかと思います。
これまで変数は単一の名前に対して単一の値しか設定してきませんでした。
例えばこれまでの知識で1週間の曜日を変数として宣言するとします。

```Javascript
var youbi1 = 'Monday';
var youbi2 = 'Tuesday';
var youbi3 = 'Wednesday';
var youbi4 = 'Thursday';
var youbi5 = 'Friday';
var youbi6 = 'Saturday';
var youbi7 = 'Sunday';
```

これを配列を使用すると

```Javascript
var youbi = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'];
```
と定義することができます。
変数を参照する場合にはどうすればよいでしょうか。

```Javascript
Logger.log(youbi[0]); //Monday
Logger.log(youbi[1]); //Tuesday
Logger.log(youbi[2]); //Wednesday
・
・
・
Logger.log(youbi[6]); //Sunday
```
のように「変数名[index]」の形で参照することができます。
代入も同様に

```Javascript
youbi[0] = 'Monday';
```
とできます。

##for文
本来はforEach文を使用するべきですが少しスクリプトに慣れてからの方が良いので、ここではfor文を使用します。
for文は繰り返し行う処理に使用されます。先ほど説明した配列を合わせて使用すると、非常に効率よく繰り返しの処理を行うことができます。

```Javascript
for (i=0; i<7; i++) {
  Logger.log(youbi[i]);
}
```
この例では、youbiに定義したそれぞれの曜日の文字列をLogger.logで出力していきます。
データの構造は同じで内容は異なっているけど、同じ処理を行いたいときに使用します。
for (i=0; i<7; i++) {
という見慣れない記述ですが、順番に説明していきます。
forはfor文の開始です。（）の後ろの「{」から「}」までの間の処理を繰り返し行います。
()内の最初のi=0はfor文内で使用する変数を初期化しています。
この例では変数「i」を0で初期化しています。
続く「;」は初期化の処理を区切る記述です。
次の「i<7」は、変数「i」が7より小さい場合にfor文の処理を行うという終了条件になります。
続く「;」は終了条件を区切る処理です。
最後の「i++」は、for文の処理を一回実行するごとに1回実行される処理になります。
ここでは変数iをインクリメントしています。(i = i + 1　とほぼ同義)

for文の構造をまとめると、
for (初期化処理;終了条件;処理ごとに実行される処理)
となります。
それぞれ省略が可能ですがここでは割愛します。

今回のスクリプトでは、変数iを0で初期化、i<7の条件を満たしている間は繰り返し処理を行い、一回処理を行うごとにiを1ずつ増やす、という内容になっています。
その結果配列youbiのindexが0から6の内容をLogger.logで出力するという動作を行います。

新しい要素の説明はここまでです。
これらを踏まえて早速スプレッドシートとカレンダーの連携を行うスクリプトを見ていきましょう。

#今度こそスクリプト
下記のスクリプトをスクリプトエディタを起動してコピーします。

```Javascript
function addEventsToCalendar(){
  //project.propertyを使うとスッキリするよ
  const ROW_INDEX_HEADER = 1;
  const ROW_INDEX_DATA_START = 2;

  const COL_INDEX_DATA_START = 1;
  const COL_INDEX_DATA_END = 13;
  const COL_INDEX_REGIST = 13;

  const DATA_INDEX_START_DATETIME = 0;
  const DATA_INDEX_END_DATETIME = 1;
  const DATA_INDEX_EVENTNAME = 2;
  const DATA_INDEX_LOCATION = 10;
  const DATA_INDEX_DESCRIPTION = 11;
  const DATA_INDEX_REGIST = 12;
    
  const TARGET_SHEET_NAME = '管理';
  const CONDITION_REGIST_STRING = '登録する';
  const WRITE_REGIST_STRING = '登録済み';
  
  const CALENDER_ID = "xxxxxx@gmail.com";
  
  var calender = CalendarApp.getCalendarById(CALENDER_ID);
  
  var activeSheet = SpreadsheetApp.getActiveSpreadsheet();
  var sht = activeSheet.getSheetByName(TARGET_SHEET_NAME);

  var dataStartRow = ROW_INDEX_DATA_START;
  var dataEndRow = sht.getLastRow();
  var numDataRows = dataEndRow - dataStartRow + 1;
  var data = sht.getRange(dataStartRow, COL_INDEX_DATA_START, numDataRows, COL_INDEX_DATA_END).getValues();

  var updateRegistColList = [];
  var modified = false;
  for(i=0; i<numDataRows; i++){
    var regist_string = data[i][DATA_INDEX_REGIST];
    if (regist_string == CONDITION_REGIST_STRING) {
      var start = new Date(Utilities.formatDate(data[i][DATA_INDEX_START_DATETIME],"JST","yyyy/MM/dd HH:mm:ss"));
      var end = new Date(Utilities.formatDate(data[i][DATA_INDEX_END_DATETIME],"JST","yyyy/MM/dd HH:mm:ss"));

      calender.createEvent(data[i][DATA_INDEX_EVENTNAME], start, end, {location: data[i][DATA_INDEX_LOCATION], description: data[i][DATA_INDEX_DESCRIPTION]});
      regist_string = WRITE_REGIST_STRING;
      modified = true;
    }
    updateRegistColList.push([regist_string]);
  }
  if (modified) {
    sht.getRange(dataStartRow, COL_INDEX_REGIST, numDataRows, 1).setValues(updateRegistColList);
  }
}

//PCだとボタンが押せるけど、スマホは押せないので、特定のセルが編集されたタイミングで更新する
function executeAddEventsToCalendar(){
  const COL_INDEX_SMARTPHONE_REGIST = 17;
  const ROW_INDEX_SMARTPHONE_REGIST = 2;

  var mySheet = SpreadsheetApp.getActiveSheet(); //シートを取得
  var myCell = mySheet.getActiveCell(); //アクティブセルを取得
 
  if(myCell.getColumn()==COL_INDEX_SMARTPHONE_REGIST && myCell.getRow() == ROW_INDEX_SMARTPHONE_REGIST){ //アクティブセルがF列かを判定
    addEventsToCalendar();
  }
}

```
少し長いですが一つずつ確認していきましょう。

addEventsToCalendar関数はスプレッドシートで用意したデータをカレンダーへ登録するための関数です。
この関数ではスプレッドシート上の全てのデータを取得、取得したデータのうち登録状況のセルが「登録する」となっているデータを、指定したしたCalenderIDをもつカレンダーへ入力されているデータに従い登録を行なったあと、登録状況の内容を更新するという内容で構成されています。
詳細を見ていきましょう。

```Javascript
const ROW_INDEX_HEADER = 1;
const ROW_INDEX_DATA_START = 2;
```

ROW_INDEX_HEADERはデータのヘッダー情報のインデックスになります。スプレッドシートでは１番上の行の番号は1となります。
ROW_INDEX_DATA_STARTはデータの開始される行番号になります。ヘッダー情報が1行しかないため2行めからデータを開始します。

```Javascript
const COL_INDEX_DATA_START = 1;
const COL_INDEX_DATA_END = 13;
const COL_INDEX_REGIST = 13;
```
COL_INDEX_DATA_STARTはデータの始まる列のインデックスになります。スプレッドシートでは一番左の列の番号は1となります。
COL_INDEX_DATA_ENDはデータが終了するインデックスになります。
COL_INDEX_REGISTは「登録状況」の列のインデックスになります。COL_INDEX_DATA_ENDと同じ値になっていますが、登録状況を一番後ろのデータとして入力するようにしたためです。データの構成によっては調整する必要がありますので気をつけましょう。

```Javascript
  const DATA_INDEX_START_DATETIME = 0;
  const DATA_INDEX_END_DATETIME = 1;
  const DATA_INDEX_EVENTNAME = 2;
  const DATA_INDEX_LOCATION = 10;
  const DATA_INDEX_DESCRIPTION = 11;
  const DATA_INDEX_REGIST = 12;
```
スプレッドシートから取得した情報を参照する際のインデックスが入っています。
スプレッドシート上では行も列もそれぞれ開始は１からとなっていました。
GoogleAppsScriptで複数件のデータを扱うときには先ほど説明した「配列」を使用します。
特別な場合を除き、配列のindexは0から割り振られるため、DATA_INDEX_START_DATETIMEは0となっています。
DATA_INDEX_START_DATETIMEは開始時刻のデータ
DATA_INDEX_END_DATETIMEは終了時刻のデータ
DATA_INDEX_EVENTNAMEはカレンダーに表示される名前のデータ
DATA_INDEX_LOCATIONは場所のデータ
DATA_INDEX_DESCRIPTIONは詳細のデータ
DATA_INDEX_REGISTは登録状況のデータ
上記のデータを参照するためのインデックスを定義しています。

```Javascript
  const TARGET_SHEET_NAME = '管理';
  const CONDITION_REGIST_STRING = '登録する';
  const WRITE_REGIST_STRING = '登録済み';
```
TARGET_SHEET_NAMEはデータを入力してあるシート名になります。
CONDITION_REGIST_STRINGは登録処理を行う対象の条件になる文字列を定義しています。
WRITE_REGIST_STRINGは登録処理を行なった後に登録済みデータとしてステータスを変更する際に使用される文字列を定義しています。

```Javascript
  const CALENDER_ID = "xxxxxx@gmail.com";
  
  var calender = CalendarApp.getCalendarById(CALENDER_ID);
```
CALENDER_IDは登録する先のカレンダーIDを定義しています。
ここは使用する人によって異なるので自分が使用するカレンダーIDで置き換えてください。
var calender = CalendarApp.getCalendarById(CALENDER_ID);
は指定したカレンダーIDでカレンダーを取得してcalender変数に代入し初期化しています。

```Javascript
  var activeSheet = SpreadsheetApp.getActiveSpreadsheet();
  var sht = activeSheet.getSheetByName(TARGET_SHEET_NAME);
```
見慣れないスクリプトが出てきました。
Logger.logを使用するときにもありましたがここはこういう記述なので今は深く考えなくて大丈夫です。
SpreadSheetAppはスプレッドシートを使用しやすくなるように用意されている機能です。
getActiveSpreadsheet()に関してですが、今回書いているスクリプトはここでしか使用しないスプレッドシートと1対1で紐づいています。使用するスプレッドシートを取得する処理と考えてください。

var activeSheet = SpreadsheetApp.getActiveSpreadsheet();
はactiveSheet変数にスプレッドシートを代入して初期化しています。

var sht = activeSheet.getSheetByName(TARGET_SHEET_NAME);
はスプレッドシートからシート名が「TARGET_SHEET_NAME」のものを取得し、sht変数に代入して初期化を行うという処置になります。TARGET_SHEET_NAMEは「管理」と定義していたので「管理」という名前のシートを取得します。

```Javascript
  var dataStartRow = ROW_INDEX_DATA_START;
  var dataEndRow = sht.getLastRow();
  var numDataRows = dataEndRow - dataStartRow + 1;
  var data = sht.getRange(dataStartRow, COL_INDEX_DATA_START, numDataRows, COL_INDEX_DATA_END).getValues();
```
dataStartRowはデータの開始する行を表す変数です。
var dataEndRow = sht.getLastRow();
は先ほど取得した「管理」シートでデータが存在する最後の行の番号を取得してdataEndRowに代入し初期化しています。
var numDataRows = dataEndRow - dataStartRow + 1;
はデータの最終行とデータの開始行からデータの件数を取得してnumDataRowsに代入し初期化しています。
var data = sht.getRange(dataStartRow, COL_INDEX_DATA_START, numDataRows, COL_INDEX_DATA_END).getValues();
のsht.getRange(dataStartRow, COL_INDEX_DATA_START, numDataRows, COL_INDEX_DATA_END)は、スプレッドシートで言う所の範囲選択になります。dataStartRowはROW_INDEX_DATA_STARTを代入していたので2、COL_INDEX_DATA_STARTは1、numDataRowsはデータの件数、COL_INDEX_DATA_ENDは13でした。
sht.getRange(2, 1, numDataRows, 13)となります。
もう少し詳細に説明すると、最初の2は2行目、次の1はスプレッドシートのA列、numDataRowsはデータの件数、最後の13はM列となり、スプレッドシートでの表記をすると、「A2:M[1+データ数]」の範囲選択と同義になります。
データ数が1件の場合は「A2:M2」、データ数が２件の場合は「A2:M3」です。
そしてgetValues()はgetRangeで範囲選択したセルを対象に入力されている値を取得します。

```Javascript
  var updateRegistColList = [];
  var modified = false;
```
var updateRegistColList = [];
は登録処理を行なった後に登録状況を更新するための変数を入れておく用途で使用するための配列を初期化しています。
var modified = false;
は登録状況で変更のあるデータが存在したかどうかを表すフラグ用に用意した変数です。

```Javascript
  for(i=0; i<numDataRows; i++){
```
先ほど用意した変数numDataRowsには処理を行う対象のデータの数が入っています。
そのためこのfor文ではデータ数文だけ後続する処理を行うという意味になります。

```Javascript
    var regist_string = data[i][DATA_INDEX_REGIST];
```
i件目の登録状況のデータに入力されている文字列をregist_stringに代入し初期化しています。

```Javascript
    if (regist_string == CONDITION_REGIST_STRING) {
```
変数regist_stringの値とCONDITION_REGIST_STRINGを比較して一致する場合に「{」から「}」までの間の処理が実行されます。CONDITION_REGIST_STRINGは「登録する」と定義していたので、regist_stringが「登録する」という文字列であった場合に「{」から「}」までの処理が実行されます。

```Javascript
      var start = new Date(Utilities.formatDate(data[i][DATA_INDEX_START_DATETIME],"JST","yyyy/MM/dd HH:mm:ss"));
      var end = new Date(Utilities.formatDate(data[i][DATA_INDEX_END_DATETIME],"JST","yyyy/MM/dd HH:mm:ss"));
```
ここではスプレッドシートから取得した開始時刻と終了時刻のデータからカレンダーへ登録する際の日付に変更する処理になります。

```Javascript
      calender.createEvent(data[i][DATA_INDEX_EVENTNAME], start, end, {location: data[i][DATA_INDEX_LOCATION], description: data[i][DATA_INDEX_DESCRIPTION]});
      regist_string = WRITE_REGIST_STRING;
      modified = true;
```
calender.createEventですが、カレンダーに登録する処理になります。
繰り返し文を行う前にcalender変数を初期化しましたがその変数を使用しています。
i件目のdataに設定されている名前、先ほど初期化した開始時刻と終了時刻、その次の「{」で始まるものは連想配列と呼ばれます。
連想配列は配列と似ていますが配列が数値で参照したのに対して連想配列では文字列で参照を行います。
今回は「{」「}」で挟まれる部分にlocationとdescriptionという名前で変数を用意し、それぞれi件目のデータの場所と詳細を渡して処理を実行しています。
またこの処理でカレンダーへの登録を行なったため、regist_stringの内容を「登録する」から「登録済み」へ変更し、modifiedの内容をtrueで上書きします。

```Javascript
    }
```
登録処理の終了の閉じ括弧「}」です。

```Javascript
    updateRegistColList.push([regist_string]);
```
updateRegistColListは配列として定義していましたが、配列はpushという関数を使用することができます。
pushを呼び出すと配列の末尾に新しい変数を追加することができます。
ここでは[regist_string]という形で、regist_string変数を配列に入れた形で末尾に挿入します。
これは後述するsetValues関数を使用する際に、例えば１行のデータであっても、１列のデータであっても、指定できる変数は配列である必要があるためとなります。
例えば5件のデータの「登録状況」を更新する場合、updateRegistColListの内容は

```Javascript
[
  ["登録する"],
  ["登録する"],
  ["登録済み"],
  ["登録済み"],
  ["登録する"]
]
```
という構造の配列となっている必要があります。
updateRegistColListははじめ空の配列として宣言しました。
そして１件ずつpush([regist_string])を呼び出すことで

```Javascript
//初期化時
[
]
//1回push
[
  ["登録する"]
]
//2回push
[
  ["登録する"],
  ["登録する"]
]
//3回push
[
  ["登録する"],
  ["登録する"],
  ["登録済み"]
]
//4回push
[
  ["登録する"],
  ["登録する"],
  ["登録済み"],
  ["登録済み"]
]
//5回push
[
  ["登録する"],
  ["登録する"],
  ["登録済み"],
  ["登録済み"],
  ["登録する"]
]
```
と言った経過を辿ることになります。
スクリプトに戻ります。

```Javascript
  }
```
繰り返し処理の閉じ括弧「}」です。

```Javascript
  if (modified) {
    sht.getRange(dataStartRow, COL_INDEX_REGIST, numDataRows, 1).setValues(updateRegistColList);
  }
```
もし一度でも登録処理を行なっている場合、modifiedはtureのはずなのでこの中の処理が実行されることになります。
ここで行なっている処理は、入力されているデータ数分「登録状況」のデータを更新するという処理になります。
先ほどupdateRegistColListは5件のデータが入っている想定で説明を行いましたが、実際のスクリプトでは件数が変化してもその件数に合わせて処理を行う必要があるため、numDataRowsという変数でデータの件数を制御しています。
getValues()ではデータを取得しましたがここではデータの更新処理を行うためにsetValues()を呼び出しています。
sht.getRangeは先ほど冒頭で範囲指定と同義と説明しました。
sht.getRange().getValues()がキーボード操作やマウス操作でのクリップボードへのコピーの操作であるとすれば、setValues()はペーストの操作と言えます。
範囲選択した箇所へupdateRegistColListを書き込み重複してカレンダーへの登録を行わないように制御しています。

以上でスクリプトに関しての説明は一旦終了です。
今回はこのスクリプトの実装に合わせてスプレッドシートのデータを用意して実際に実行してみます。

#データの入力
<img width="1183" alt="スクリーンショット 2019-12-24 10.54.42.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/423369/7a00c78c-959d-67a8-682e-aa10726692ff.png">
世間はクリスマス真っ盛りですが、私の予定表はすでにその先です。
今回は少し遅めの忘年会の予定を入れてみました。
スクリプトエディタから「addEventsToCalendar」を選択して実行してみます。
<img width="443" alt="スクリーンショット 2019-12-24 11.03.24.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/423369/d528a69b-a714-40fa-6143-854ef38b902c.png">

登録されました。ただ登録するときに、スクリプトエディタを開いて実行するという手順が若干面倒です。

#実行ボタンのようなものを配置
スプレッドシートの上部にツールバーがありますが、その中から「挿入-図形描画」と選択します。<img width="824" alt="スクリーンショット 2019-12-24 11.07.20.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/423369/12f3b7cd-d4ab-4087-c86c-0e2e770b5159.png">
こんな画面が表示されます。
この中から「図形」アイコンを選択してクリックします。
図形をマウスオーバーするとたくさんの図形が表示されます。
<img width="505" alt="スクリーンショット 2019-12-24 11.09.41.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/423369/8666cc83-918f-de52-beb4-353d1eacaa80.png">
好きな図形を選択しましょう。押せそうな図形であればなんでも良いです。
<img width="286" alt="スクリーンショット 2019-12-24 11.12.35.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/423369/a37f6ac1-9769-c6b6-7aff-8f89130468a0.png">
テキストも配置してみました。「保存して終了」を押します。
すると先ほどの図形がスプレッドシートに配置されます。
<img width="314" alt="スクリーンショット 2019-12-24 11.15.56.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/423369/e5834629-4ff5-3b64-1ab7-c8a094bba466.png">
この図形のあたりをクリックします。
<img width="246" alt="スクリーンショット 2019-12-24 11.16.43.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/423369/9f9be28f-e292-7da6-b4dd-8bd62109df7a.png">
少しわかりにくいのですが、右上に縦に3つ並んだ点が見えるでしょうか。
ここをクリックします。
「スクリプト割り当て」という項目があるので選択しましょう。

<img width="399" alt="スクリーンショット 2019-12-24 11.19.01.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/423369/bdf8cef7-cd24-3dc0-b89e-21dcdaa81df8.png">
テキストには「addEventsToCalender」を指定します。
次回からはこのボタンを押すだけでカレンダーに登録を行うことができます。

#スマホからも登録できるようにしたい
先ほど作成した実行ボタンのようなものだとスマホからスプレッドシートアプリを使用しても押すことができません。
そのため以下のようなスクリプトを追加します。

```Javascript
//PCだとボタンが押せるけど、スマホは押せないので、特定のセルが編集されたタイミングで更新する
function executeAddEventsToCalendar(){
  const COL_INDEX_SMARTPHONE_REGIST = 17;
  const ROW_INDEX_SMARTPHONE_REGIST = 2;

  var mySheet = SpreadsheetApp.getActiveSheet(); //シートを取得
  var myCell = mySheet.getActiveCell(); //アクティブセルを取得
 
  if(myCell.getColumn()==COL_INDEX_SMARTPHONE_REGIST && myCell.getRow() == ROW_INDEX_SMARTPHONE_REGIST){ //アクティブセルがF列かを判定
    addEventsToCalendar();
  }
}
```
COL_INDEX_SMARTPHONE_REGISTの17はQ列を、ROW_INDEX_SMARTPHONE_REGISTの2は2行目を表しています。
スマホからセルの編集はできるので、編集を行なったタイミングでそれが特定のセルであった場合にaddEventsToCalendarを実行するという仕組みを作ります。
スクリプトを追加したら保存します。
次にスクリプトエディタのツールバーから「編集-現在のプロジェクトのトリガー」と選択します。
<img width="268" alt="スクリーンショット 2019-12-24 11.41.53.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/423369/f0ac6def-3a12-737a-f7ce-b265ff78559d.png">
遷移した画面の先で右下の方に「トリガーを追加」という青いボタンが表示されているのでクリックします。
<img width="215" alt="スクリーンショット 2019-12-24 11.43.25.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/423369/a53fac80-380a-8398-645e-198f2fe9a413.png">
<img width="664" alt="スクリーンショット 2019-12-24 11.45.01.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/423369/2b83951d-9c75-d1b2-fb6f-56af17ef1ce0.png">
実行する関数を先ほど追加した「executeAddEventsToCalendar」
イベントのソースを「スプレッドシートから」
イベントの種類を「変更時」
と設定して保存します。
スクリプトにはQ列の2行目のセルを指定していたので、
<img width="310" alt="スクリーンショット 2019-12-24 11.47.05.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/423369/c8d87140-a535-3fec-8cac-2410234149d5.png">
このようにスプレッドシートへコメントを書いておきました。
これでQ列の2行目のセルの値を変更すればスマホのスプレッドシートアプリからでもカレンダーの登録を実行することができます。

#まとめ
今回はおまけ編としてスプレッドシートとカレンダーの連携について紹介しました。
意外と簡単だったでしょうか、それとも難しかったですかね・・・。
私もGoogleAppsScriptを使ってみたいという気持ちはあったのですが、何のために使うのかという動機の部分が全くありませんでした。ライブのスケジュールとチケットの管理の煩わしさを解消するための仕組みづくりというちょうどいい題材ができたため、今回のスプレッドシートとカレンダーの連携の実装を思いつきました。
皆さんも身近にあるちょっとした不便さを課題として解消するという目的でGoogleAppsScriptを触ってみるのはいかがでしょうか(笑)

おしまい