HTML内のjavascriptからGAS（コード.gs）の関数を呼び出す

# 動機

スプレッドシート上で作ってる論文DBを論文1本ずつ閲覧できるページを作りたい。まあ、ちょっとしたWebアプリです。

# やりたいこと
ページのトップに「次へ」と「戻る」ボタンがあって、それを押すと一個ずつ表示される論文が移り変わっていく。

# 準備

## スプレッドシート
スプレッドシートは、2つのシートから構成。
### 論文DB
1枚は論文DBそのもので、著者名やタイトルなどの情報が並ぶ。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/317916/3eb8b3e7-5511-3961-a5fb-19c83c8db05f.png)

### 閲覧画面
もう1枚はこういう感じ。もともとはこの2枚目のシートを閲覧画面にして、ボタンをgsと結びつけてB1セルの数値を動かして、スプレッドシートのvlookup関数を使って順に論文データを表示させようとしていたのだが、iPadのスプレッドシートアプリだと、ボタンが動いてくれなかったので、Webアプリ化することにした。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/317916/84bc5b57-1ab5-eedf-5eb8-88c80330f588.png)

なので、この「閲覧画面」シートのB1セルを、どこまで読んだかの記憶場所にすることにする。
そうすると、Webアプリの画面だけでなく、このスプレッドシートの閲覧画面でも読めるようになる。

## コード.gs
つづいてスプレッドシートから「ツール」->「スクリプトエディタ」。
コード.gsに以下のコードを書き込む。

```{Javascript}
function doGet() { 
  myHTML = HtmlService.createTemplateFromFile("MyReader.html");
  return myHTML.evaluate(); 
}
```

## HTML+Javascript
んで、エディタのファイルメニューからhtmlを作成（MyReader.html）。内容は以下のような感じ。
p要素の中に色々と書き込んでいきたいのでjQueryも読み込んでおいた。

```{html:MyReader.html}
<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
  </head>
  <body>
    <input type="button" id="Back_btn" value="前へ" >
    <input id="PaperNo" name="PaperNo" type="text" />
    <input type="button" id="Next_btn" value="次へ" >
    <P id="eTitle"/>
    <P id="jTitle"/>
    <P id="VolIssue"/>
    <P id="Year"/>
    <P id="eAbst"/>
    <P id="jAbst"/>
  </body>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
<script type="text/javascript">
</script>
</html>
```
これでデプロイして、https://script.google.com/a/**************/exec にアクセスすると、MyReader.htmlの内容が表示される。まあ、この時点では見ての通りボタンとテキストボックスが表示されるだけ。

# HTMLのJavascriptからコード.gsの関数にアクセス

同じプロジェクト内にあるのであれば、`google.script.run.関数`でHTML（JavaScript）側からコード.gs内の関数を実行できる。
ただ、戻り値を持っている処理の場合にはちょっとややこしい。結論を言うと、次のように関数実行に成功した場合と失敗した場合の処理（ハンドラ）を噛ませてから関数を実行しなければならない.

```{javascript}
google.script.run
  .withSuccessHandler(/***/)
  .withFailureHandler(/***/)
  .myFunction();
```
/***/の箇所は、別に定義したコールバック関数名でも良いし、`function(){}`というのでコールバック処理を直接書く形でもよい。また、コード.gsで定義しているmyFunction()の戻り値は、各ハンドラが受けとっており、そのままコールバック関数の引数として渡される。

# 実装
処理としては、webアプリにアクセスして、myReader.htmlが読み込まれると、まずはスプレッドシートの「閲覧画面」シートのB1セルの値を取りに行く(`getPaperNo()`)。`getPaperNo()`が成功したときにはハンドラは`SuccessGetNo(paperno)`を呼び出す。引数のpapernoは`getPaperNo()`の戻り値。
値取得に成功したら、その値をhtmlのテキストボックスに入れるとともに、その値をキーにして、論文DB側からデータを読み込む(`getPaperInfo(paperno)`)。`getPaperInfo(paperno)`が成功したときにはハンドラは`SuccessGetInfo(paperinfo)`を呼び出す。paperinfoは`getPaperInfo(paperno)`の戻り値で、JSONオブジェクトになってる。
「次へ」ボタンや「前へ」ボタンが操作された場合には、これまたもともと「閲覧画面」上のボタンと結びつけた関数として用意していた`forward()`や`back()`をgoogle.script.runを使って呼び出し、成功したら、番号を取得。さらにそれも成功したら論文データを上記と同じ手順で読み込む。

ということで、作ったのが、それぞれ以下の通り。

```{Javascript: コード.gs}
function forward() {  //   もともと閲覧画面の「次へ」ボタン用に作った関数
  var ss = SpreadsheetApp.openById("*************"); // スプレッドシートを取得
  var sheet = ss.getSheetByName("閲覧画面");
  myrange = sheet.getRange('B1');
  myvalue = myrange.getValue();
  myrange.setValue(myvalue+1);
  return;
}

function Back() { 　//   もともと閲覧画面の「前へ」ボタン用に作った関数
  var ss = SpreadsheetApp.openById("*************"); // スプレッドシートを取得
  var sheet = ss.getSheetByName("閲覧画面");
  myrange = sheet.getRange('B1');
  myvalue = myrange.getValue();
  if(myvalue ==1)   return;
  myrange.setValue(myvalue-1);
  return;
}

function doGet() { 
  myHTML = HtmlService.createTemplateFromFile("MyReader.html");
  return myHTML.evaluate(); 
}

function GetPaperNo(){
  var ss = SpreadsheetApp.openById("*************"); // スプレッドシートを取得
  var sheet = ss.getSheetByName("閲覧画面");
  var myrange = sheet.getRange('B1');
  var myvalue = myrange.getValue();
  return myvalue;
}

function GetPaperInfo( PaperNo ){
  var ss = SpreadsheetApp.openById("*************"); // スプレッドシートを取得
  var sheet = ss.getSheetByName("シート1");
  var Author = sheet.getRange(PaperNo+1,2).getValue();
  var eTitle = sheet.getRange(PaperNo+1,3).getValue();
  var jTitle = sheet.getRange(PaperNo+1,4).getValue();
  var volissue = sheet.getRange(PaperNo+1,5).getValue();
  var year = sheet.getRange(PaperNo+1,6).getValue();
  var page = sheet.getRange(PaperNo+1,7).getValue();
  var ISSN = sheet.getRange(PaperNo+1,8).getValue();
  var DOI = sheet.getRange(PaperNo+1,9).getValue();
  var Link = sheet.getRange(PaperNo+1,10).getValue();
  var eAbst = sheet.getRange(PaperNo+1,11).getValue();
  var jAbst = sheet.getRange(PaperNo+1,12).getValue();
  
  var json = {
    "Author":Author,
    "eTitle":eTitle,
    "jTitle":jTitle,
    "volissue":volissue,
    "year":year,
    "page":page,
    "ISSN":ISSN,
    "DOI":DOI,
    "Link":Link,
    "eAbst":eAbst,
    "jAbst":jAbst
  }
  return json;
}
```

```{html:myReader.html}
<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
  </head>
  <body>
    <input type="button" id="Back_btn" value="前へ" >
    <input id="PaperNo" name="PaperNo" type="text" />
    <input type="button" id="Next_btn" value="次へ" >
    <P id="eTitle"/>
    <P id="jTitle"/>
    <P id="VolIssue"/>
    <P id="Year"/>
    <P id="eAbst"/>
    <P id="jAbst"/>
  </body>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
<script type="text/javascript">
var Back_btn = document.getElementById('Back_btn');
var Next_btn = document.getElementById('Next_btn');

$(document).ready( function(){
// ページ読み込み時に実行したい処理
  google.script.run
      .withSuccessHandler( SuccessGetNo )
      .withFailureHandler( Failure )
      .GetPaperNo(); 
});

function Failure(){
  alert("失敗");
}

function SuccessGetNo(PaperNo){  // PaperNoはこの呼び出し元になっているGetPaperNoの戻り値
  alert(PaperNo);
  $("#PaperNo").val(PaperNo);
  google.script.run
    .withSuccessHandler(SuccessGetInfo)
    .withFailureHandler(Failure)
    .GetPaperInfo(PaperNo)
}

function SuccessGetInfo(PaperInfo){
  $("#eTitle").text("原題："+PaperInfo.eTitle);
  $("#jTitle").text("邦題："+PaperInfo.jTitle); 
  $("#VolIssue").text("巻号："+PaperInfo.volissue); 
  $("#Year").text("発刊年： "+PaperInfo.year);
  $("#eAbst").text("Abstract： "+PaperInfo.eAbst);
  $("#jAbst").text("日本語訳： "+PaperInfo.jAbst);

}

Back_btn.addEventListener('click', function(){ // スプレッドシートの方のカウンタを一つ戻したうえで、GetNo、GetPaperInfoをする。
  google.script.run
    .withSuccessHandler(function(){
      google.script.run
        .withSuccessHandler( SuccessGetNo )
        .withFailureHandler( Failure )
        .GetPaperNo(); 
    })
    .withFailureHandler(Failure)
    .Back()
});

Next_btn.addEventListener('click', function(){
  google.script.run
    .withSuccessHandler(function(){
      google.script.run
        .withSuccessHandler( SuccessGetNo )
        .withFailureHandler( Failure )
        .GetPaperNo(); 
    })
    .withFailureHandler(Failure)
    .forward()
});

</script>
</html>

```