Googleのサービスだけでメールの一括送信ツールを作ってみた。その２

前回：[Googleのサービスだけでメールの一括送信ツールを作ってみた。](https://qiita.com/P3117/items/3d7ccb934597ae39c247)

#1 前回からの変化
前回メールの送信ツールを作ってからしばらくして、注文者から追加要望があったので忘備録として残します。
前回からの修正は以下の通り。

１．CC、BCC、添付ファイルを設定できるように修正
２．HtmlServiceを使用したプレビュー機能追加
３．下書きの作成機能追加
４．上記の修正に伴う、スプレッドシートの修正

それでは１つずつ解説します。

#2 スプレッドシートのレイアウト
まず初めにスプレッドシートはこうなりました。
![スクリーンショット (1).png](https://qiita-image-store.s3.amazonaws.com/0/309466/40de4c33-e65c-5fe5-0688-8306b29f05bc.png)

プレビューボタンと下書きボタンが追加されて、送信ボタンの位置が変わりました。
CCとBCC、添付ファイルはそれぞれカンマ区切りで設定するようにしました。
*添付ファイルはドキュメントIDで指定するので事前にドライブに格納しておく必要があります。

元々実装する気はなかったのですが、使用者が使うかどうかわからないけど欲しいとのことだったのでこうなりました。

#3 プレビュー機能の実装
これまではメールの宛先(TO)、本文をちゃんと確認してから使用してくださいね！
と念押してたのですが、流石に色々後から付け足したため、事前確認を強化する必要があると思いました。

結果としてプレビューボタンを押下すると以下のようなプレビュー画面を表示するようにしました。
![スクリーンショット (2).png](https://qiita-image-store.s3.amazonaws.com/0/309466/564e7319-0a96-e176-ac25-5221b6f42150.png)
![スクリーンショット (3).png](https://qiita-image-store.s3.amazonaws.com/0/309466/758b1c28-4e89-236c-dc9a-02b870909f3a.png)
![スクリーンショット (4).png](https://qiita-image-store.s3.amazonaws.com/0/309466/89c1c374-c38b-a56f-7983-7ebc565138eb.png)

このプレビュー画面の実装にはGASのHtmlServiceという機能を使用しました。
あらかじめ用意したHTMLをIframeのようにビューの中に埋め込んでくれるような感じです。
デザインにはBootstrap4をちょろっと使用しました。
宛先の欄が空行になっているのは念のため宛先を設定していないからです。

コード的にはこんな感じです。

```抜粋.
function onClickPreviewButton() {
  //★PreviewButton★
  Logger.log("★★onClickPreviewButton★★") 
  
  inputCheck();
  readAddress();
  parseTmpFile();

  var ss = SpreadsheetApp.getActiveSpreadsheet();
  
  //GASプロジェクト内のPreview.htmlをベースに画面を作る(HtmlOutputオブジェクト)
  var html = HtmlService.createTemplateFromFile('Preview');
  
  //<HtmlOutput>.変数名でHTML側に引き渡せる値を指定できる
  html.To = sendList;
  html.Cc = Cc.split(",");
  html.Bcc = Bcc.split(",");    
  html.mailTitle = mailTitle;
  html.mailBody = docObj.getBody().getText();
  html.Temp = TempFileList;
  
  //HtmlOutput形式でshowする
  //.evaluate()でHTMLの解析・展開を行う
  ss.show(html.evaluate().setWidth(1000).setHeight(500));
}
```

GAS側から参照されるHTMLは

```Preview.html
<!DOCTYPE html>
<html>
  <head>
    <title>プレビュー画面</title>
    <base target="_top">
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">
  </head>
  <body>
    <!--タブ部分-->
    <div class="p-3">
      <!-- タブボタン部分 -->
      <ul class="nav nav-tabs">
        <li class="nav-item">
          <a href="#tab1" class="nav-link active" data-toggle="tab">宛先</a>
        </li>
        <li class="nav-item">
          <a href="#tab2" class="nav-link" data-toggle="tab">件名・本文</a>
        </li>
        <li class="nav-item">
          <a href="#tab3" class="nav-link" data-toggle="tab">添付ファイル</a>
        </li>
      </ul>

      <!--タブのコンテンツ部分-->
      <div class="tab-content">
        <div id="tab1" class="tab-pane active">
          <div class="card">
            <div class="card-header">TO【重要】</div>
              <ul id="TOList" class="list-group list-group-flush">
                <? for (var i = 0; i < sendList.length; i++) { ?>
                  <li class="list-group-item"><?= "("  ?><?= sendList[i][0] ?><?= ")" ?><?= sendList[i][1] ?><?= " : " ?><?= sendList[i][2] ?></li>
                <? } ?>
              </ul>
          </div>
          <div class="card">
            <div class="card-header">CC【重要】</div>
            <ul id="CCList" class="list-group list-group-flush">
                <? for (var i = 0; i < Cc.length; i++) { ?>
                  <li class="list-group-item"><?= Cc[i] ?></li>
                <? } ?>
            </ul>
          </div>
          <div class="card">
            <div class="card-header">BCC【重要】</div>
            <ul id="BCCList" class="list-group list-group-flush">
                <? for (var i = 0; i < Bcc.length; i++) { ?>
                  <li class="list-group-item"><?= Bcc[i] ?></li>
                <? } ?>
            </ul>
          </div>
        </div>
        <div id="tab2" class="tab-pane">
          <div class="card">
            <div class="card-header">件名</div>
            <ul class="list-group list-group-flush">
              <li class="list-group-item"><?= mailTitle ?></li>
            </ul>
          </div>
          <div class="card">
            <div class="card-header">本文</div>
            <ul class="list-group list-group-flush">
              <li class="list-group-item"><?= mailBody ?></li>
            </ul>
          </div>  
        </div>
        <div id="tab3" class="tab-pane">
          <div class="card">
            <div class="card-header">添付ファイル</div>
            <ul class="list-group list-group-flush">
              <? for (var i = 0; i < Temp.length; i++) { ?>
                <li class="list-group-item"><?= "("  ?><?= Temp[i][0] ?><?= ") "  ?><?= Temp[i][1] ?></li>
              <? } ?>
            </ul>
          </div>  
        </div>
      </div>
    </div>
  </body>
  <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js" integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49" crossorigin="anonymous"></script>
  <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js" integrity="sha384-ChfqqxuZUCnJSK3+MXmPNIyE6ZbWh2IMqE241rYiqJxyMiZ6OW/JmZQ5stwEULTy" crossorigin="anonymous"></script>
</html>
```
<? ?>で囲っている箇所はスクリプトレットです。jspとかやっている人はイメージつきやすいかと思います。
GAS側で設定した変数やスクリプトを埋め込むことができます。
また今回はscriptタグを直接書いていますが、分けたい場合は

```
<?!= HtmlService.createHtmlOutputFromFile('<HTMLファイル名>').getContent(); ?>
```
上記のスクリプトレットを挿入したい場所に記入し、読み込むスクリプトやスタイルを

```script.html
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js" integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js" integrity="sha384-ChfqqxuZUCnJSK3+MXmPNIyE6ZbWh2IMqE241rYiqJxyMiZ6OW/JmZQ5stwEULTy" crossorigin="anonymous"></script>
```

```style.html
//サンプル
<style>
  p {
    line-height: 2em;
    padding-bottom: 0.2em;
    width: 20em;
  }
  .blue-with-white-text {
    background-color: #3F51B5;
    color: rgba(255, 255, 255, 0.9);
  }
  .qiita-green {
    background-color: #48BE05;
    color: rgba(255, 255, 255, 0.9);
  }
  .red-with-white-text {
    background-color: #E53935;
    color: rgba(255, 255, 255, 0.9);
  }
</style>
```

とすることで別ファイルに分けることができます。
私が試してみた感じ、GASのプロジェクトにはファイルのインポートができないみたいなのでCDN経由の読み込み＋このやり方での管理がやりやすかったです。
テンプレートとか個人でバリバリデザインやライブラリを書いて使うぜ、って人はどうするんでしょう。

#4 下書きの作成機能の実装
２．で送信するメールの情報を全体的に確認して、下書き機能で実際に送信するメールのイメージを作るという思想になりました。

こちらはGmailAppの機能で簡単に作れます。

```抜粋.
function mailTest() {
  Logger.log("★★mailTest★★");
  
  makeDraft();
  
  //下書き作成
  mailHeader = "〇〇株式会社" + " " + "□□□" + "様\n\n"
  //宛先,件名,本文,(オプション)
  GmailApp.createDraft("",mailTitle,(mailHeader + mailBody),mailArgs);
  
}

function makeDraft() {
  Logger.log("★★makeDraft★★");

  //宛先
  mailHeader = "";

  //本文
  mailBody = docObj.getBody().getText();
  
  //添付ファイル
  var TempObjList = [];
  for (var i = 0; i < TempFileList.length; i++) {
    TempObjList.push(TempFileList[i][2]);
  }
  
  //オプション(Cc,Bcc,添付ファイル)
  mailArgs = {cc:Cc,bcc:Bcc,attachments: TempObjList};

}
```

これでGmailの下書きが１つできます。
![スクリーンショット (5).png](https://qiita-image-store.s3.amazonaws.com/0/309466/999fc1e1-cd0e-273e-01cc-dafca7bf7234.png)

TOは設定してませんが、CC・BCC、件名、本文、添付ファイルが設定されています。
GoogleドキュメントやGoogle関連のファイルは添付時に一律PDFになるようです。
.txtやExcel、Wordは元々の拡張子で添付されます。

#5 おまけ
機能が増えたため、単体試験を考えていたところ
QUnit for Google Apps Scriptというものを知りました。

昔からJavaScriptの単体試験に使われているもののようですが、GASでも使えるようです。
導入方法や実装例はこちら[Qunit](https://github.com/simula-innovation/qunit/tree/gas/gas)
テストしやすいコードの重要さを改めて実感させられました。。。。
次回からはQUnitの使用も踏まえた実装にしようと思います。
イメージは↓↓↓の感じ

```
function doGet( e ) {
  QUnit.urlParams( e.parameter );
  QUnit.config({ title: "Unit tests for my project" });
  QUnit.load( myTests );
  return QUnit.getHtml();
};

QUnit.helpers(this);

function myTests() {
  module("main.gs");

  test("dummy test", 1, function() {
    ok(true);
  });
  

  //JUnitでいうassertEqualsみたいな感じ。
  test("maxRowCount", 1, function(assert) {
    assert.ok( maxRowCount(), "401" );
  });
}
```