DevFest 2015 GASハンズオン


この資料は2015/06/20に行われた GTUG Devfest IO報告会 石巻会場ブレイクアウトセッションの資料と同様の内容を持ってきたものです。

実際の資料は以下です。

* Documents版
  * https://docs.google.com/document/d/1ySq6N92xs9E8zDpIBPHXhdfNOUsYOM5w0DdOvxLgfwk/edit

※Qiitaの方が探しやすいのと検索引っかかりやすいのでここに残させて頂きます。
# 今日のゴール
今回の「ブレイクアウトセッション」ではGoogle Apps Script(以降GAS)の魅力が体験しやすい
「メール送信」「Spreadsheetの拡張」をメインテーマとして「Spreadsheetを使ったメール配信アプリケーション」を作成します。

完成形は以下のSpreadsheetの感じになります。
https://docs.google.com/spreadsheets/d/1r5xX64BrZdjkmacC9xf5nx5zrzslb74OH9g1382L0Es/edit#gid=0
> ※注意：実際に使用する場合は、メニューの「ファイル」→「コピーを作成」からコピーを作成し、新しく作成されたファイルを使用して試してみてください。

![完成イメージ](https://qiita-image-store.s3.amazonaws.com/0/2965/75ed1293-074c-7789-b24c-99152b5e66d1.png "メール配信アプリケーション_devfest_-_Google_スプレッドシート_と_Kobito.png")

# ハンズオン

ここからハンズオンを開始します。
できるな～と思う方はガンガン進んでいってしまってください。
細々コードを載せていますが、特に断りがない場合はコピーせず、書いてみてください。
GASの開発環境であるScript Editorの補完(Ctrl + Space)が体験でき、
これからの開発イメージに繋がるはずです。

またどうしてもうまく動かない、ヒントが欲しい場合は講師や隣の人に聞いて下さい。
または以下のリポジトリに各StepごとにTAGが作られたコードが有ります。
こちらも参照してみてください。

https://github.com/soundTricker/devfest-2015-google-apps-script-handson

## Step0) 準備
まずSpreadsheet上でGASを作成するためにSpreadsheetの作成と、Script Editorの起動を行います。
### Spreadsheetの作成
以下のリンクをクリックし新しいSpreadsheetを作成します。
https://docs.google.com/spreadsheets/
>※もちろんGoogle Drive上から作成して頂いても構いません。
>※作成されたファイルはGoogle Driveのマイフォルダ内に存在します。
>※今回のハンズオンでは先日リリースされた「新しいSheets」を利用します。
>新しいSheets出ない場合はScriptがうまく動作しない場合があります。

画面左上のファイル名をクリックし、任意の名前を設定します。

![ファイル名の修正](https://googledrive.com/host/0BwzWIlBMCiR9M2tlTHlZLU5PT0E/img/snap2.png)

以下のようにデータを入力しておきます。 ※色付けや罫線は任意です。
A1セル : メールアドレス
B1セル : 氏名

![カラムの作成](https://googledrive.com/host/0BwzWIlBMCiR9M2tlTHlZLU5PT0E/img/snap3.png)
### Script Editorの起動
GASの開発環境であるScript Editorを起動します。
メニューから「ツール」→「スクリプトエディタ」を選択します。

![Script Editorの起動](https://googledrive.com/host/0BwzWIlBMCiR9M2tlTHlZLU5PT0E/img/snap4.png)
Script Editorが起動するので、Spreadsheetの時と同様に左上のファイル名(プロジェクト名)をクリックし、
任意の名称に変更して下さい。
Script Editor起動時に、「開始画面」が表示された場合は「閉じる」ボタンをクリックして下さい。

![Script Editor](https://googledrive.com/host/0BwzWIlBMCiR9M2tlTHlZLU5PT0E/img/snap5.png)
![開始画面は閉じて下さい。](https://googledrive.com/host/0BwzWIlBMCiR9M2tlTHlZLU5PT0E/img/snap6.png)
以上で開発の準備は完了です。
## Step1) メールの送信
GASの魅力の一つにとても簡単にメールを送信ができるという点があります。
初めてGASのコーディングをする人でも、すぐに出来ますのでやってみましょう。

### コードの作成(MailApp)
以下のコードをmyFunction関数の下に記述してCtrl + Sキーなどで保存して下さい。

```javascript:コード.gs
function sendMail() {
  var email = Session.getActiveUser().getEmail();
  
  MailApp.sendEmail(email , "初めてのGoogle Apps Script", "Hello World. Welcome Google Apps Script!");
}
```
### スクリプトの認可と起動
これから作成した関数を起動してメールの送信を行いますが、その前にメールを送信するための権限を取得します。

GASではメール送信やGmail内のデータ取得、その他Google関連の多くのAPIを扱う為 各種APIの認可が必要です。
通常GAS以外からこれらのGoogle APIを叩く場合はOAuth2などの認可フローが必要となりますが、
GASの場合は自身のコードで利用しているAPIを判定し、
利用しているAPIに沿った認可フローを自動で実施してくれます。

要は開発者は画面をポチポチするだけで良いのです。
#### スクリプトの認可
Script Editorの上の方にあるSelectボックスをクリックし「sendMail」(先ほど作成した関数)を選択します。

「▶」をクリックします。(Ctrl + r でも動作します)

![認可](https://googledrive.com/host/0BwzWIlBMCiR9M2tlTHlZLU5PT0E/img/snap7.png)

「承認が必要です。」というダイアログが表示されるので「続行」をクリックします。

![承認ダイアログ](https://googledrive.com/host/0BwzWIlBMCiR9M2tlTHlZLU5PT0E/img/snap8.png)

認可画面が表示されるので「承認する」をクリックします。

![認可画面](https://googledrive.com/host/0BwzWIlBMCiR9M2tlTHlZLU5PT0E/img/snap9.png)

以下の画面が表示されたら認可は完了です。 このウィンドウは閉じてしまって下さい。
(閉じるボタンで閉じれない場合があります。その時はブラウザの「×」ボタンなどで閉じて下さい。)

![完了画面](https://googledrive.com/host/0BwzWIlBMCiR9M2tlTHlZLU5PT0E/img/snap10.png)

#### スクリプトの起動
次に作成した関数を起動します。
といっても手順は先に実施した「スクリプトの認可」と同じです。
「スクリプトの認可」はスクリプトの起動時に必要であれば実施されるということなのです。

Script Editorの上の方にあるSelectボックスをクリックし「sendMail」(先ほど作成した関数)を選択します。
「▶」をクリックします。

![スクリプトの起動](https://googledrive.com/host/0BwzWIlBMCiR9M2tlTHlZLU5PT0E/img/snap7.png)
今ログインしているGoogleアカウントのGmailを確認して下さい。メールが届いているはずです!
赤いエラー帯が表示された方は何かが間違っているかもしれません。
お隣の方や講師の人に聞いてみてください。

## Step2) メニューの作成とメニューからのスクリプトの起動
ここまでの実装で、動作可能な状態になりました。しかし、実際にこのメール配信アプリケーションを
使う場合には毎回Script Editorを起動する必要があります。そのような手順を行うのは現実的ではありません。
GASではSpreadsheetのメニューに任意のファンクション呼び出しを追加することができます。
メニューを操作することで、自由にファンクションの呼び出しが可能になります。
### メニューの作成
以下のコードをScript Editorの任意の場所に記述して下さい。 ※コメントは解説なので不要です。

```javascript:コード.gs
function onOpen() {

  SpreadsheetApp.getUi() //①UIオブジェクトの取得
  .createMenu("メール配信") //②Spreadsheetに「メール配信」メニューを追加
  .addItem("メールを送信", "sendMail") //③メニュー内にアイテムを追加、sendMail関数を呼ぶように指定
  .addToUi();  //④実際に"Spreadsheet"へ追加 ※ここを呼ばないと追加されません.

} 
```
#### コード解説
①のSpreadsheetAppはSpreadsheetを扱うためのGoogleが用意したオブジェクトです。
getUi()メソッドを呼び出しUIオブジェクトと呼ばれるSpreadsheet等のUIを操作する為のオブジェクトを取得しています。
UIオブジェクトを利用するとAlertの表示、Promptの表示、メニューの追加/削除、カスタムモーダル/カスタムサイドバーの表示などが行えます。

②以降では実際にSpreadsheetへメニューを追加しています。
以前は別の方法でSpreadsheetへメニューを追加していましたが最新のコードではこちらになりますので、ご注意下さい。
②のcreateMenuメソッドでSpreadsheetのメニューエリアにメニューを追加し、
③のaddItemでメニュー内の項目とその項目を選択した際に呼び出される関数を設定しています。
今回は先ほど作成したsendMail関数を指定しています。
そして④で実際にSpreadsheetへメニューを追加しています。

このコード全般を見ると気がつくかもしれませんが、GASではメソッドチェーンで連続的に関数が呼び出せる事が多いです。

### onOpen関数
今回は関数名を`onOpen`としました。
このonOpen関数はGASの中では特殊な関数名で、この名前で関数を作成すると"Spreadsheet"起動時に
誰が起動したとしても自動的に呼び出されるようになります。

実際にさきほど作成したSpreadsheetをリロードして、自動的にonOpenが呼び出されるか確認してみてください。
メニューが追加されていればOKです。
>※SpreadsheetをリロードするとScript Editorが閉じます。


![追加されたメニュー](https://googledrive.com/host/0BwzWIlBMCiR9M2tlTHlZLU5PT0E/img/snap11.png)

うまくメニューが追加されていない場合はエラーが起きている可能性があります。
一度onOpen関数をScript Editor上から先ほどの手順で、直接起動してみてください。
### メニューからのスクリプトの起動
Spreadsheetのメニューに先ほど作成したメニューが追加されていれば起動は簡単です。
メニューから「メール配信」→「メールを送信」をクリックして下さい。
先程と同様にメールが送信されるはずです！

## Step3) Spreadsheetからデータの読み込み
次にSpreadsheetから実際にデータを読み込み、一行ずつメールアドレスを取得し、メールを送信します。
Spreadsheetに先ほど作成したメールアドレスカラムと氏名に任意のメールアドレス(送信、確認が可能なもの)と氏名を複数行入力しておいて下さい。
>※Gmailのエイリアス機能を使って youremail+任意@gmail.comなどとしておくと確認がしやすいです。

またSpreadsheetのシート名を「メール配信」に修正しておいて下さい。
>※シート名は下の方にあるシートタブをダブルクリックして修正可能です。

![シート名の変更](https://googledrive.com/host/0BwzWIlBMCiR9M2tlTHlZLU5PT0E/img/snap12.png)
### Spreadsheetからデータが入力されている範囲を読み込みメール送信
先ほど作成したsendMail関数を以下のように修正します。※コメントは不要です。

```javascript:コード.gs
function sendMail() {
  
  //メールデータ
  var mailForm = {
    subject : "テスト To ${氏名}さん",
    body : "Hello ${氏名}さん"
  };
  
  //① Spreadsheetの「メール配信」シートから配信先の取得
  var sheet = SpreadsheetApp.getActive().getSheetByName("メール配信");
  
  //② sheet.getDataRange()はデータが入力されている範囲を取得します。
  //range.getValues()でその範囲に入力されているデータを2次元配列で取得します。
  var values = sheet.getDataRange().getValues();
  
  //1行目はヘッダーなので, データ行は2行目からです。※headerは使いません。
  var header = values[0];
  for(var i = 1; i < values.length; i++) {
    
    //③ 行を取得し、1列目(index = 0)のメールアドレスを取得
    var row = values[i];
    var to = row[0];
    
    //④ 氏名置換
    var nameReplaceRegex = /\$\{氏名\}/gm;
    
    var body = mailForm.body.replace(nameReplaceRegex, row[1]);
    var subject = mailForm.subject.replace(nameReplaceRegex, row[1]);
    
    //メール配信
    MailApp.sendEmail(to, subject, body);
    
    //⑤ 連続で送るとエラーになるので少し待たせます。
    Utilities.sleep(100);    
  }
  
  
  //⑤ 完了した旨をSpreadsheetに表示します。
  SpreadsheetApp.getUi().alert("メール送信が完了しました");
}



```
#### コード解説
今回のコードはSpreadsheetのセルからメールアドレスデータを読み込みメール配信を行っています。
色んな処理を追加しているのでちょっと長いです。
①で今回作成したSpreadsheetを取得し(`Spreadsheet.getActive()`)、更に"メール配信"シートを取得しています。

②で`sheet.getDataRange()`を呼び出しシート内でデータが入力されている範囲(Rangeオブジェクト)のみを取得しています。
このメソッドを使うことで不要なデータ取得をせずに必要なデータを取得することができます。
またRangeオブジェクトの`getValues()`メソッドを呼び出して、
対象範囲に記載されたすべてのデータを二重配列で取得しています。

GASはほとんどのメソッド呼び出しがGoogle APIをネットワーク経由で呼び出しています。
その為めあまりにも大量にgetRange()やgetValue()を呼び出すとその度に通信が発生してしまい、
非常にスクリプトが遅くなります。
可能であればこれらの呼び出しはなるべく少なくなるようにコードを記述したほうがよいでしょう。
>※ただし2014/06/10現在、新しいSheetsではこのgetValues()による効果は低くなっています。

②〜③でSpreadsheetからデータを取得し、一行ずつ読み込んでいます。
③でデータが入っている二重配列から1行ずつ取り出し、1列目にあるメールアドレスを取得しています。
④の氏名の置換は通常のJavascriptのコードなので今回のGASハンズオンでは気にしないでください。
この後、先ほど同様にメール送信をし⑤で一度処理を一時停止しています。
これはGASの一定時間内でのメール送信限界数に引っかからないようにするための対策コードです。

そして⑥でUIオブジェクトを利用してメール送信が完了した旨を表示しています。
### テスト
関数を修正したらScriptEditorから起動してテストしてみてください。
メールが一気に配信されるはずです。


## Step4) サイドバーの作成
先ほどのメール配信スクリプトではメールタイトルやメール本文が固定でこのままでは利用できません。
なのでSpreadsheetにこれらを入力するサイドバーを表示し、
そこから先ほどを作成したスクリプトを起動するようにしてみましょう。

### UIを作成(サイドバー用HTMLの作成)
GASではUIを作成する場合大きく分けて2つの方法が有ります。
1つ目はUiAppと呼ばれるオブジェクトを使い、コードでUIを書いていく方法
2つ目はHtmlServiceと呼ばれるオブジェクトを利用しHTMLでUIを作成して、それを読み込む方法です。

今回のハンズオンではHtmlServiceを利用してUIを作成します。

Script Editorのメニューから「ファイル」→「新規作成」→「HTMLファイル」を選択

![HTMLファイルの作成](https://googledrive.com/host/0BwzWIlBMCiR9M2tlTHlZLU5PT0E/img/snap13.png)
ファイル名の入力ダイアログが表示されるので「sidebar」と設定します。

![HTMLファイル名の設定](https://googledrive.com/host/0BwzWIlBMCiR9M2tlTHlZLU5PT0E/img/snap14.png)
sidebar.htmlというHTML作成用のファイルがScript Editor内に作成されるので下記コードをコピペして保存します。※今回のハンズオンではHTMLの中身は範囲外の為です。ただコメントは読んでおいてください！

```html:sidebar.html
<!-- GASのテンプレートでは<html>タグや<body>タグは不要です。(正確にはあると若干表示速度が下がります。) -->
<!-- add-ons cssはGoogleが提供しているGAS用CSSでSpreadsheetやDocs, FormのUIに近いL/Fを提供します。-->
<!-- 各種使える定義や見た目は https://developers.google.com/apps-script/add-ons/css を参照して下さい -->
<link rel="stylesheet" href="https://ssl.gstatic.com/docs/script/css/add-ons.css">

<style>
.width-100 {
  width: 100%;
}
.logo {
  vertical-align: middle;
}
</style>

<div>

  <div class="sidebar branding-below">
    <form id="mail-form">
      <div class="block form-group">
        <label for="mail-subject"><b>メールタイトル</b></label>
        <input type="text" name="subject" placeholder="こんにちは ${氏名}" id="mail-subject" class="width-100">
      </div>
      <div class="block form-group">
        <label for="mail-body"><b>メール本文</b></label>
        <textarea id="mail-body" name="body" class="width-100" rows="15" placeholder="To ${氏名} ... ${置換文字1}"></textarea>
        <span class="secondary">${カラム名}で置換されます。</span>
      </div>

    <div class="block" id="button-bar">
      <button class="blue" id="send-mail-button">送信</button>
      <button id="send-test-mail-button">テスト送信</button>
    </div>
    </form>
   </div>

  <div class="sidebar bottom">
    <span class="logo"><img alt="Add-on logo" class="logo" width="64" height="36"
      src="https://googledrive.com/host/0BwzWIlBMCiR9M2tlTHlZLU5PT0E/img/GTUGG_white.png"></span>
    <span class="gray">GTUG Girls Meetup #17</span>
  </div>
</div>

<script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>


```
### UIの表示
HTMLを作成したらこれを表示する関数を作成します。
まず以下の関数を作成します。

```javascript:コード.gs
function showSidebar() {
  
  //HTMLファイルを読込み ※引数のファイル名は.htmlを省略可能です。
  //.setSandboxMode(HtmlService.SandboxMode.IFRAME) はおまじないです。(口頭で解説します。)
  var sidebarHtml = HtmlService.createHtmlOutputFromFile("sidebar").setTitle("メール配信").setSandboxMode(HtmlService.SandboxMode.IFRAME);
 
  //SpreadsheetApp上で表示
  SpreadsheetApp.getUi().showSidebar(sidebarHtml);
  
}
```

次にonOpen関数で設定した項目設定時に呼び出す関数を今作成した`showSidebar`関数に変更します。

```javascript:コード.gs
function onOpen() {

  SpreadsheetApp.getUi() //UIオブジェクトの取得
  .createMenu("メール配信") //Spreadsheetに「メール配信」メニューを追加
  .addItem("サイドバーを表示", "showSidebar") //←この行を修正
  .addToUi(); //実際に"Spreadsheet"へ追加 ※ここを呼ばないと追加されません.
}
```

修正が完了したらメニューからサイドバーを表示してみてください。
以下の様なサイドバーが表示されたら成功です！

![サイドバーの表示](https://qiita-image-store.s3.amazonaws.com/0/2965/734fb7a8-4b63-840a-9e62-b2f13c9abd60.png "メール配信アプリケーション_devfest_-_Google_スプレッドシート.png")

#### コード解説
sidebar関数内ではHtmlServiceオブジェクトを使用して、
`createHmltOutputFromFile`メソッドでsidebar.htmlを読み込んでいます。

今回のハンズオンでは利用しませんが、HtmlServiceにはテンプレートエンジン機能もあり初期値の設定や、
GASで読み込んだデータをUIに表示することも可能です。
そしてUIオブジェクトに渡してサイドバーを表示しています。
HtmlServiceで作成したオブジェクトはGASでWebアプリケーションを作成する場合も利用します。
こちらに関してはこの記事を参照して下さい。

## Step5) サイドバーからスクリプトの起動
UIを作成したら、このUI上からGASの関数を呼び出します。
このUIは実際にはクライアントのブラウザー上で動いているので、Ajaxのような通信をしサーバサイドに有るGASを呼び出すことになりますが、GASではこれらを簡単に行う仕組みが有ります。
### google.script.run (座学)
HtmlServiceを利用してUIを作成する際、HTML上(今回で言えばsidebar.html上)では`google.script.run`というJavascriptオブジェクトが利用可能になります。
この`google.script.run`には以下の様な大きく分けて2種類(+α)のメソッドがあります。

* Server側にある関数を(Script Editor上で作成した関数)呼び出すメソッド
  * Server側で作成した関数名と同じ関数名、引数で呼び出せる。
* Server側のコードを呼び出した後のコールバックを設定するメソッド
  * 成功時、失敗時用があり、共に戻り値には`function`を渡す
  * 成功時： withSuccessHandler、失敗時：withFailureHandler

例えばServer側(コード.gs内)にgetHoge(fuga)という関数が有り、それをクライアント側(sidebar.html)から非同期呼び出しを行いたい場合は以下の様なコードになります。

```javascript:コード.gs
function getHoge(fuga) {
  return {message : fuga};
}
```

このServer側の関数をクライアント側から呼び出す最小のコードは以下になります。

```javascript:google.script.run.js
google.script.run.getHoge("test");
```

ただし上記は戻りを処理していない為、何が起きたかわよくわかりません。
実際には以下の様なコードになるでしょう。

```html:sidebar.html
<div id="hoge"></div>
<button id="callHogeButton">getHogeを呼ぶ</button>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script>
$(function{}(
  $("#callHogeButton").click(function(){
    google.script.run
      .withSuccessHandler(function(result) {
        //getHogeが例外をスローしなっかった場合withSuccessHandlerが呼び出される。
        //resultにはgetHogeが返却した{message : fuga}が入っている
        $("#hoge").text(result.message);
      })
      .withFailurHandler(function(error) {
        //getHogeが例外をスローした場withFailureHandlerが呼び出される。
        //errorにはErrorオブジェクトが入っている
        alert(error);
      })
      .getHoge("test"); //Server側と同じ関数名で呼び出せる。ただしprivate関数(関数名末尾が_)は呼び出せない
  });
));
</script>

```

またServer側を呼び出す際に渡す引数に、formタグのelementを渡すと自動的にform内のinput要素名を利用したJSONオブジェクトを作成してくれます。

```html:sidebar.html
<div id="hoge"></div>
<form id="form">
  <input name="name" type="text">
  <input name="type" type="text">
  <input name="fuga" type="text">
</form>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script>
$(function{}(
  $("#callHogeButton").click(function(){
    google.script.run
      .withSuccessHandler(function(result) {
        //getHogeが例外をスローしなっかった場合withSuccessHandlerが呼び出される。
        //resultにはgetHogeが返却した{message : fuga}が入っている
        $("#hoge").text(result.message);
      })
      .withFailurHandler(function(error) {
        //getHogeが例外をスローした場withFailureHandlerが呼び出される。
        //errorにはErrorオブジェクトが入っている
        alert(error);
      })
      .getHoge($("#form").get(0)); //Server側には{name : "nameの入力値", type: "typeの入力値", fuga: "fugaの入力値"} というオブジェクトが渡される
  });
));
</script>
```



### 実装
では実際にsidebar.htmlにコード.gs内に実装した`sendMail`関数を呼び出すJavascriptコードを追加してみましょう。
以下のコードをsidebar.htmlの一番下に追記して下さい。

```html:sidebar.html
<script>
(function($, global, undefined){
  var $mailSubject = $("#mail-subject"),  //チェック用メールタイトル
  $mailBody = $("#mail-body"), //チェック用メール本文
  $buttonBar = $("#button-bar"), //エラー表示用
  $mailForm = $("#mail-form"); //フォーム、sendMailを呼び出す際に利用
  
  //clickイベント追加
  $(function(){
    $("#send-mail-button").click(sendMail);
  });
  
  //タイトルと本文のチェック
  function validateForm(){
    var hasError = false;

    if($mailSubject.val() == "") {
      showError("メールタイトルは必須です。", $mailSubject); 
      hasError = true;
    }
    
    if($mailBody.val() == "") {
      showError("メール本文は必須です。", $mailBody); 
      hasError = true;
    }
    return hasError;
  }
  
  //メール送信処理(送信ボタンのイベントハンドラ)
  function sendMail(){
  
    //ボタンを非活性に
    this.disabled = true; //this === $("#send-mail-button")
    
    //エラー表示を一度削除
    $(".error").remove();
    
    //バリデーションをしてエラーが有る場合はボタンを戻して終了
    if(validateForm()) {
      this.disabled = false;
      return;    
    }
    
    //Server側呼び出し
    google.script.run
    .withSuccessHandler(function(result, element){ //成功時
      element.disabled = false;
    })
    .withFailureHandler(function(error, element){  //失敗時
      showError(error, $buttonBar);
      element.disabled = false;
    })
    .withUserObject(this) //with****Handlerの第二引数を設定
    .sendMail($mailForm.get(0));  //Server側を実際に呼び出し
  }
  
  //エラー表示
  function showError(msg, element) {
    var div = $('<div class="error">' + msg + '</div>');
    $(element).after(div);
  }
})(jQuery, window);
</script>
```

次にServer側のsendMail関数を引数が受け取れるように修正します。

```javascript:コード.gs
function sendMail(mailForm) { //←mailFormというプロパティにsubject、body(共にsidebar.htmlのinputのname)を持つオブジェクトを引数にする

//↓不要になるので削除
//メールデータ
//  var mailForm = {
//    subject : "テスト To ${氏名}さん",
//    body : "Hello ${氏名}さん"
//  };
  //パラメータチェック
  validateMailForm_(mailForm);



  //↓以降は当分そのまま
  //① Spreadsheetの「メール配信」シートから配信先の取得
  var sheet = SpreadsheetApp.getActive().getSheetByName("メール配信");
  
  //② sheet.getDataRange()はデータが入力されている範囲を取得します。
  //range.getValues()でその範囲に入力されているデータを2次元配列で取得します。
  var values = sheet.getDataRange().getValues();
  
  //1行目はヘッダーなので, データ行は2行目からです。※headerは使いません。
  var header = values[0];
  for(var i = 1; i < values.length; i++) {
    
    //③ 行を取得し、1列目(index = 0)のメールアドレスを取得
    var row = values[i];
    var to = row[0];
    
    //④ 氏名置換
    var nameReplaceRegex = /\$\{氏名\}/gm;
    
    var body = mailForm.body.replace(nameReplaceRegex, row[1]);
    var subject = mailForm.subject.replace(nameReplaceRegex, row[1]);
    
    //メール配信
    MailApp.sendEmail(to, subject, body);
    
    //⑤ 連続で送るとエラーになるので少し待たせます。
    Utilities.sleep(100);    
  }

  //⑤ 完了した旨をSpreadsheetに表示します。
  SpreadsheetApp.getUi().alert("メール送信が完了しました");

  //↑ここまでそのまま 値を返却するように 修正(ただし未使用)
  return {message : "メール送信が完了しました"};
}

/**
 * メールの内容をチェックします。
 * @param {object} mailForm メールの内容 subject:タイトル, body:本文
 */
//関数名の最後に_(アンダースコア)が付いているとその関数はプライベート関数になります。
//プライベート関数は上部の関数呼び出しSelectBoxに表示されない、google.script.runで呼び出せないなどの特徴があります。
function validateMailForm_(mailForm) {

  if (mailForm.subject == ""){
    throw new Error("メールタイトルは必須です。");
  }
  
  if (mailForm.body == ""){
    throw new Error("メール本文は必須です。");
  }
}
```

以上でServer側の呼び出しコードの追加は完了です。
実際にサイドバーを表示し、タイトル、本文を入力、「送信」ボタンをクリックしてテストしてみてください。

## Appendix
ここまでで今回のハンズオンで取り扱う内容は終了となります。
以降はこれじゃぁ物足りないという方や、実はまだこのアプリケーションは完成していないのでそういった部分をAppendixという形で記載します。宿題としてやってみてください。

### Appendix 1) テスト送信機能の追加
毎回Spreadsheetシート内に記載されたメールアドレスへの一括送信は不安ですね？
一度自分のメールアドレス宛に送信してくれるテスト送信機能を追加しましょう。
既にテスト送信用のボタンはsidebar.html内にあるので
1. Server側(コード.gs)にテスト送信用関数の追加 (自分のメールアドレス宛にメールを送信する)
2. Client側(sidebar.html)に1.で追加した関数を呼び出すコードを追加
が必要です。
> ヒント: 自分のメールアドレス宛はSession.getActiveUser().getEmail()で取得可能です。

### Appendix 2) Aboutダイアログの作成
誰が作ったかわからないアプリケーションを使うのは不安ですよね?
追加したメニューにAbount項目を追加し、
モーダルダイアログで「このアプリケーションについて」を表示できるようにしましょう。
> ヒント: メニューの追加はonOpen関数内で、モーダルダイアログはUIオブジェクトのui.showModalDialog(userInterface, title)で表示できます。

### Appendix 3) 送信記録の追加
メールを送信した際現状はいつ送信したのか本当に送信されたのか不明な状態です。
Spreadsheetへ新たに「送信ステータス」「送信日時」カラムを追加し、
メールを送信するごとに結果を記載するようにしましょう
> ヒント: セルへの値の書き込みはrange.setValue()にて行えます。

### Appendix 4) (ちょいムズ) より柔軟な置換文字列
実際にアプリケーションを利用し始めると氏名の置換だけでは物足りなくなってきます。
任意にカラムを追加でき、カラム名にあらせて${カラム名}の形で置換文字が利用できるようにしましょう
> ヒント: 本質的にはこのAppendixはGASに関係ありません。sendMailないで利用していなかったheader変数を利用しましょう。

### Appendix 5) (難問) 本文や、タイトルの保存
本文やタイトルがサイドバーを表示する度に初期化されからの状態になってしまっています。
送信する度に前回のデータとして保存しておき、次回サイドバー表示時に初期値として表示されるようにしましょう。
> ヒント: データを保存するにはPropertyServiceが役に立ちます。また初期値を表示するにはsidebar.htmlのテンプレート化が必要です。


# 参考資料 & ハンズオンのフルコード
## 参考資料

* 本資料
  * http://qiita.com/soundTricker/items/da32d1ce257060c4ef66
* GASのリファレンス
  * https://developers.google.com/apps-script/
* ハッシュタグ( twitter or G+)
  * #gasja
* コミュニティ
  * Apps API Japan
      * https://groups.google.com/forum/#!forum/google-apps-api-japan

## ハンズオンのコード

ハンズオンのコードはGithubに保存されています。
Diff等を見る場合に活用して下さい。

* 完成形
  * https://github.com/soundTricker/devfest-2015-google-apps-script-handson


この資料は大橋啓介 a.k.a soundTricker318 が作成しました。