VueからGAS関数でスプレッドシートにデータを入力

こちらの記事は
[GASとVueでサーバレス社内チーム向け工数管理](https://qiita.com/taromorimotohf/items/711d01557c7b08b842f1)

の詳細記事その④です。

前回はスプレッドシートのデータをVue側でデータバインディングを行なって、
親子関係を保ったプルダウンを実装しました。
話を元に戻しますが工数管理を行うため、
ユーザーが対応した案件まとめて１日単位でフォームから投稿してもらって、
そのデータをスプレッドシートに取り込むというのが今回のゴールです。


##対応した案件を工数付きでまとめて入力してもらう。
```
日付、ユーザー名（今回はメールアドレスで対応）、クライアント名、プロジェクト名、案件名、工数、
```

これが１レコードという感じで最後にまとめてフォームから投げてもらうイメージです。

##ではレッツほにゃらら

**code.gs**

```
///////htmlの取得と表示
function doGet() {
  var template = 'index';
  return HtmlService.createTemplateFromFile(template).evaluate();
}
///////インクルードを使えるように
function include(filename) {
  return HtmlService.createHtmlOutputFromFile(filename).getContent();
}
///////クライアント一覧を取得
function getSheetDataCliant(){
  var spreadSheetID = "スプレッドシートID";
  var sheetName = "クライアント";
  var res = SpreadsheetApp.openById(spreadSheetID).getSheetByName(sheetName).getDataRange().getValues();
  //２次元配列にする
  res = Array.prototype.concat.apply([], res);
  return res;
}
///////引数からプロジェクトを配列で取得
function findProjectList(val){
  var spreadSheetID = "スプレッドシートID";
  var sheetName = "プロジェクト";
  var dat = SpreadsheetApp.openById(spreadSheetID).getSheetByName(sheetName).getDataRange().getValues(); //受け取ったシートのデータを二次元配列に取得
  // 引数のクライアント名がある行を取得
  for(var i=0;i<dat.length;i++){
    if(dat[i][0] === val){
      var clinantRow = i+1;
    }
  }
  //getRangeで引数がある行の2列目から10列目まで取得
  var res = SpreadsheetApp.openById(spreadSheetID).getSheetByName(sheetName).getRange(clinantRow, 2, 1, 10).getValues();
  //２次元配列にする
  res = Array.prototype.concat.apply([], res);
  //空白の配列を除去
  res = res.filter(function(e){return e !== "";});
  return res;
}
///////引数から案件を配列で取得
function findCaseList(val,val2){
  var spreadSheetID = "スプレッドシートID";
  var sheetName = "案件";
  var dat = SpreadsheetApp.openById(spreadSheetID).getSheetByName(sheetName).getDataRange().getValues(); //受け取ったシートのデータを二次元配列に取得
  var _cliantArray = [];
  //クライアント名が重複するため、引数1の同名のクライアント名があるセルの行を一旦配列に入れる
  for(var i=0;i<dat.length;i++){
    if(dat[i][0] === val){
     _cliantArray.push(i);
   }
  }
  //引数１のクライアント名と 引数２のプロジェクト名がある行を取得
  for(var i=0;i<_cliantArray.length;i++){
    var _i = _cliantArray[i];
    if(dat[_i][1] === val2){
      var projectRow = _i+1;
      break;
    }
  }
  //getRangeで引数１,2がある行の３列目から10列目まで取得
  var res = SpreadsheetApp.openById(spreadSheetID).getSheetByName(sheetName).getRange(projectRow, 3, 1, 10).getValues();
  //２次元配列にする
  res = Array.prototype.concat.apply([], res);
  //空白の配列を除去
  res = res.filter(function(e){return e !== "";});
  return res;
}

/////// データをスプレッドシートに挿入
function addMasterSheetData(addUserMasterData){
  var spreadSheetID = "スプレッドシートID";
  var sheetName = '工数計算用シート';
  var values = SpreadsheetApp.openById(spreadSheetID).getSheetByName(sheetName).getRange('A:A').getValues(); //受け取ったシートのデータを二次元配列に取得
  //シートの最終行を取得
  for1:
  for (var i = values.length - 1; i >= 0; i--) {
    for(var j = 0; j < values[i].length; j++){
      if(values[i][j] != ""){
        break for1;
      }
    }
  }
  //何行分、何列分のデータかを取得して.getRangeの引数を埋めてsetValueで値をスプレッドシートに挿入
  var array_rows = addUserMasterData.length;
  var array_col = addUserMasterData[0].length;
  var last_row = i +1;
  SpreadsheetApp.openById(spreadSheetID).getSheetByName(sheetName).getRange(last_row+1, 1, array_rows, array_col).setValues(addUserMasterData);
  
}

/////// カレントユーザーのメールアドレスを返す（googleログインしている前提）
function GetUserEmail() {
  var objUser = Session.getActiveUser();
  return objUser.getEmail();
}
```

**index.html**

```
<div id="app">
    {{message}}
    <input id="addDay" type="date" class="input" v-model:value="today">
    <select v-model="selected" id="cliant01">
    　クライアント:<option v-for="row in cliantArray">{{ row }}</option>
    </select>
    <select v-model="selected02" id="project01">
     　プロジェクト：<option v-for="item in projectArray">{{ item }}</option>
    　</select>
     <select v-model="selected03" id="anken01">
      案件：<option v-for="item in ankenArray">{{ item }}</option>
     </select>
     <select id="time01" v-model="time01">
      <option v-for="item in timeArray">{{item}}</option>
     </select>
                  
     <button @click="onFinish()">登録</button>
     
     <table>
    <thead>
      <tr>
      　　　　<th>Day</th>
        <th>Cliant</th>
        <th>Project</th>
        <th>Case</th>
        <th>hour</th>
        <th>Delete</th>
      </tr>
      </thead>
      <tbody>
          <tr
      is="myjob-item"
v-for="(rows, index) in showTableArray"
      v-bind:day="rows[2]"
      v-bind:cliant="rows[3]"
      v-bind:project="rows[4]"
      v-bind:anken="rows[5]"
      v-bind:time="rows[6]"
      v-on:remove="showTableArray.splice(index, 1);"
                   
    ></tr>
    </tbody>
     </table>
     <button @click="addMyAllWork()">これで全て登録</button>
     
    </div>
```
js.html

```
//コンポーネントを用意
Vue.component('myjob-item', {
  template: '\
    <tr>\
      <td>{{ day }}</td>\
      <td>{{ cliant }}</td>\
      <td>{{ project }}</td>\
      <td>{{ anken }}</td>\
      <td>{{ time }}</td>\
      <td><button v-on:click="$emit(\'remove\')">Remove</button></td>\
    </tr>\
  ',
  props: ['day','cliant','project','anken','time']
});

var vm = new Vue({
    el: "#app",
    data: {
     message:'MY料理工数',
     cliantArray: [],
     projectArray: [],
     ankenArray: [],
     selected:'',
     selected02:'',
     selected03:'',
     currentUserEmail:'',
     today:'',
     showTableArray:[],
     time01:'',
     timeArray:[
      '00:10',
      '00:20',
      '00:30',
      '00:40',
      '00:50',
      '01:00',
      '01:30',
      '02:00',
      '02:30',
      '03:00',
      '03:30',
      '04:00',
      '04:30',
      '05:00',
      '05:30',
      '06:00',
      '06:30',
      '07:00',
      '07:30',
      '08:00',
      '08:30',
      '09:00',
      '09:30',
      '10:00'
      ]
    },
    watch:{
       selected:function(now){
       //選ばれたクライアントに紐づいたプロジェクトリストを取得するGAS関数を発火させる。成功すればgetProjectList関数
       google.script.run
        .withSuccessHandler(this.getProjectList)
        .withFailureHandler(function(arg){
          alert("データの取得に失敗しました。");
        }).findProjectList(now);
       },
       selected02:function(now){
       //選ばれたクライアント、プロジェクトに紐づいた案件リストを取得するGAS関数を発火させる。成功すればgetAnkenList関数

        this.selected03 = now;
       　　google.script.run
       　　　.withSuccessHandler(this.getAnkenList)
       　　　.withFailureHandler(function(arg){
       　　　alert("データの取得に失敗しました。");
       　}).findCaseList(this.selected, now);
      }
    },
    methods: {
      // 最初のスプレッドーシートのデータインポート
      initData: function(returnData){
        this.cliantArray = returnData;
      },
      // プロジェクトリスト
      getProjectList:function(pData){
        this.projectArray = pData;
      },
      // 案件リスト
      getAnkenList:function(aData){
        this.ankenArray = aData;
      },
      // フォームの内容を配列に入れて更にDataの配列へpush
      onFinish:function(){
      this.finishDetailDataArray = [];
      var _cliant01 = document.getElementById('cliant01').value;
      var _project01 = document.getElementById('project01').value;
      var _anken01 = document.getElementById('anken01').value;
      var _time01 = document.getElementById('time01').value;
      var _setDay = document.getElementById('addDay').value;

      var _thisArray = [];
      _thisArray.push(this.currentUserEmail);
      _thisArray.push(_setDay);
      _thisArray.push(_cliant01);
      _thisArray.push(_project01);
      _thisArray.push(_anken01);
      _thisArray.push(_time01);
      this.showTableArray.push(_thisArray);
       
      },
      // 配列のデータをスプレッドシートに入れる
      addMyAllWork:function(){
      google.script.run
        .withSuccessHandler(function(arg){
        alert("データの登録に成功しました。");
        })
        .withFailureHandler(function(arg){
          console.log(arg)
          alert("データの登録に失敗しました。");
        }).addMasterSheetData(this.showTableArray);
        
      },
      //カレントユーザーのメールアドレス
      initUserData: function(userData){
        this.currentUserEmail = userData;
      }
    },
    created: function(){
      //クライアントリストを取得するGAS関数を発火させる。成功すればinitData関数
      google.script.run
        .withSuccessHandler(this.initData)
        .withFailureHandler(function(arg){
          alert("データの取得に失敗しました。");
        }).getSheetDataCliant();
        
      // 日付のフィールドに当日の日付を入れる
      var today = new Date();
      this.today = today.getFullYear() + "-" + ('0'+(today.getMonth() + 1)).slice(-2) + "-"+ ('0'+today.getDate()).slice(-2);
      
      // カレントユーザーのemailを取得するGAS関数を発火させる。成功すればinitUserData関数
      google.script.run
        .withSuccessHandler(this.initUserData)
        .withFailureHandler(function(arg){
          alert("データの取得に失敗しました。");
        }).GetUserEmail();
        
    }
  });
```

**こんな感じで溜まって最後に登録ボタンをクリックで**
<img width="705" alt="スクリーンショット 2018-08-21 16.19.57.png" src="https://qiita-image-store.s3.amazonaws.com/0/212582/5102d29a-a7b1-ebc7-a978-6e9050c9ea3c.png">

**スプレッドシート**
<img width="768" alt="スクリーンショット 2018-08-21 16.20.51.png" src="https://qiita-image-store.s3.amazonaws.com/0/212582/8b00d25c-4995-c274-8903-4eb980c49d02.png">

**動画（早回し）**
![test000_6.gif](https://qiita-image-store.s3.amazonaws.com/0/212582/d799e7f9-7dbe-b330-4b4a-5d538a504603.gif)



一応各ページのソースにはコメント記載してるので、それでほぼわかってもらえるかと思います。
今回Vueが便利だと思ったのはDataにある配列をコンポーネントで更に料理出来ちゃうところがよかったです。
（あまりコンポーネントの使い方がわからずv-forで全データを吐き出すことしかこれまでしてなかったので、）
社内で利用ということと、リリースを早くする必要があったので、ベストプラクティスというよりかは、
やりたい事はまあ出来たかなーというレベルだと思いますので、皆さんからコメント頂いて更に調整はしたいと思ってます。
※実際はフォームのバリデーション処理等も入れています。


###現時点でもどうにかしたい事（2018.8月）

* スプレッドシートの任意の行の最終列の取得がスムーズにできなかったのでバッファを持った数値で指定してる記述
* GAS関数で、毎回スプレッドシートのIDから指定しているのを直したい。（時間なかった。。。）
* dataの量が多くなったにも関わらず入れ子にせずに作ってしまってすごい見辛い。（途中でやり直す気力がなかった。）

###ゆくゆくはこうしたい
* 現在登録済みのデータはスプレッドシートで直接自分でそのセルを消すというとんでもなく大胆な処理になっているので（スプレッドシートの履歴頼み）自分の登録したデータを一覧から削除するor編集するというページも作りたい。

皆さん、こうやった方がいいよ、など教えて頂ければ幸いです。