tohokuaikiのチラシの裏

技術的ネタとか。

Google Apps ScriptでSpreadSheetをJSONにして、別途Google Driveに保存する。

ま、いろいろとやってこんな感じ。

対象となるSpreadSheetのデータ

都道府県別の小学校数の推移
北海道 青森県 岩手県 宮城県
1975年 1820 545 610 458
1976年 1898 542 602 456
1977年 1881 531 598 460
1978年 1866 531 591 462
1979年 1854 531 579 468

そのSpreadSheetのApps Script

操作トリガーはこのメニューに置く。

更新対象となるGoogle DriveJSONファイルは、ファイルIDで指定する。ファイルIDってなんや?ってことは「Google Drive上のファイルに直接アクセスする #Slack - Qiita」で分かる。

// 更新対象のGoogle DriveのファイルID
const targetFileId = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

function publishData() {
  if (!confirm('データを書き出しします。よろしいですか?')){
    return ;
  }
  
  //ローディングダイヤログ表示
  const update_with_loader_html = "update_with_loader"
  const ui = SpreadsheetApp.getUi()
  const loader = HtmlService.createTemplateFromFile(update_with_loader_html).evaluate();
  ui.showModalDialog(loader, "ファイルを書き出し処理中です。");
}

// ここはポップアップのHTML側から呼ばれる
function updateGoogleDriveFile(contents){
  const fileID = targetFileId
  const content = "execRemoteJson("+getData()+");";  
  const blob = Utilities.newBlob(content, MimeType.PLAIN_TEXT);
  Drive.Files.update({}, fileID, blob)
}


//データ取得
function getData() {  
  //データ取得するシート(現在開いているシートを指定)
  var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('シート1')

  //行(横軸)と列(縦軸)の最大数を取得
  var maxRow = sheet.getLastRow();
  var maxColumn = sheet.getLastColumn();

  //JSON用のkey
  var keys = [];

  //データ格納配列
  var data = [];

  //2行目のkeyの名前取得
  //1行目は管理しやすいよう日本語で記述し、
  //JSON用のラベルは2行目で指定しているため
  //【getRange】の第1引数は【2】
  for (var x = 1; x <= maxColumn; x++) {
    keys.push(sheet.getRange(2, x).getValue());
  }
  
  //データの取得
  //実際のデータが3行目からなので【y = 3】から開始
  //getRange()を使って、3行目1セル ~ 最終行目の最終セルを範囲指定
  var _values = sheet.getRange(3, 1, maxRow, maxColumn).getValues();
  
  for(var i = 0; i < _values.length; i++){
    let year = _values[i][0];
    if (year) {
      var json = {};
      json.year = year;
      json.data = {};
      for (var k = 1; k < _values[i].length; k++) {
        json.data[keys[k]] = _values[i][k];
      }
      data.push(json);
    }
  }

  //整形してテキストに
  return JSON.stringify(data , null, '');  
}


//スプレッドシート読み込み時に実行して
function onOpen() {
  //メニューバーにJSON出力用メニューを追加
  var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
  var entries = [{
    name : "ホームページ用に書き出し",
    functionName : "publishData"
  }];
  spreadsheet.addMenu("HP出力", entries);
};

const alert = (message) => {
  const ui = SpreadsheetApp.getUi()
  ui.alert(message, ui.ButtonSet.OK)  
}

const confirm = (message) => {
  const ui = SpreadsheetApp.getUi()
  return ui.alert(message, ui.ButtonSet.YES_NO) === ui.Button.YES
}

ポップアップのHTMLも作る

Apps Scriptの「ファイル」から+マークで作れる。

<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
    <style>
    .loader_wrapper {
    max-width: 100%;
    overflow: hidden;
    }
    .loader,
.loader:after {
  border-radius: 50%;
  width: 10em;
  height: 10em;
}
.loader {
  margin: 60px auto;
  font-size: 10px;
  position: relative;
  text-indent: -9999em;
  border-top: 1.1em solid rgba(0, 0, 0, 0.2);
  border-right: 1.1em solid rgba(0, 0, 0, 0.2);
  border-bottom: 1.1em solid rgba(0, 0, 0, 0.2);
  border-left: 1.1em solid #000;
  -webkit-transform: translateZ(0);
  -ms-transform: translateZ(0);
  transform: translateZ(0);
  -webkit-animation: load8 1.1s infinite linear;
  animation: load8 1.1s infinite linear;
}
@-webkit-keyframes load8 {
  0% {
    -webkit-transform: rotate(0deg);
    transform: rotate(0deg);
  }
  100% {
    -webkit-transform: rotate(360deg);
    transform: rotate(360deg);
  }
}
@keyframes load8 {
  0% {
    -webkit-transform: rotate(0deg);
    transform: rotate(0deg);
  }
  100% {
    -webkit-transform: rotate(360deg);
    transform: rotate(360deg);
  }
}
    </style>
  </head>
  <body>
    <script>
      google.script.run.withSuccessHandler(function(data){
          google.script.run.withSuccessHandler(function(){
              google.script.run.alert("終了しました。")
              google.script.host.close()
          }).updateGoogleDriveFile(data)
      }).getData()
    </script>
  <div class="loader_wrapper">
  <div class="loader">Loading...</div>
  </div>
    完了までこのままお待ちください。<br>
    ※データ数によっては1~3分ほどかかる場合ございます。
  </body>
</html>

google.script.run.withSuccessHandlerを使いまくってCallback地獄。google.script.runはHTML側でしか使えない。コード.gsで使いたかった。

表示する側のHTML

このJSONを何とか取ってこようとしてAjaxJSONP試したけど普通にCORSでダメだった。そりゃそーですよね。

ってことで、PHP使う。

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<script src="https://code.jquery.com/jquery-3.7.1.min.js" integrity="sha256-/JqT3SQfawRcv/BIHPThkBvs0OEvtFFmqPF/lYI/Cxo=" crossorigin="anonymous"></script>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-9ndCyUaIbzAi2FUVXJi0CjmCapSmO7SnpJef0486qhLnuZ2cdeRhO02iuK6FUUVM" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js" integrity="sha384-geWF76RCwLtnZ8qwWowPQNguL3RmwHVBC9FhGdlKrxdiJJigb/j/68SIy3Te4Bkz" crossorigin="anonymous"></script>
<title>Google Apps Script DEMO</title>
<script>
function execRemoteJson(json){
    $(function(){
        for (let i=0,j=json.length; i<j; i++){
            let option = new Option(json[i].year, json[i].year);
            $('#y').append(option);
        }
        for (let i in json[0].data){
            let option = new Option(i, i);
            $('#p').append(option);
        }
        $('#y,#p').on('change', calcNum);
    })
    
    function calcNum(){
        let y =$('#y').val(), p = $('#p').val();
        if (y && p) {
            for (let i=0,j=json.length; i<j; i++){
                if (y == json[i].year){
                    for (let j in json[i].data){
                        if (j == p){
                            $('#num').html(json[i].data[j]);
                        }
                    }
                }
            }
        }
    }
}
<?php
echo file_get_contents("https://drive.google.com/uc?id=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx")
?>
</script>
<body>
<div class="row text-center m-5">
<h2 class="mb-5 text-right">小学校の数</h2>
<div class="col-5 text-end">
年:<select id="y"><option value="">----------</option></select>
</div>
<div class="col-2 text-center">
都道府県:<select id="p"><option value="">----------</option></select>
</div>
<div class="col-1 text-end">
<span id="num">---</span></div>
</div>
</body>
</html>

感想

  • JavaScriptのノリでalert()とかconfirm()とか使えるだろとか思ったら全然ダメだった。
  • Blobにするのと、ファイルを上書きしていくのが意外とはまる。Apps Scriptの公式ドキュメントが微妙に見づらい。WEB APIとごっちゃになってる?
  • SpreadSheetにポップアップのHTML出すのが意外と面倒。HTML側でデータゴニョニョとかなんか見通し悪そうになってしまった。

参考にしたページ