👶

GASでお弁当日の管理してみた~その1~

2022/07/17に公開

働く母の幼稚園問題

私には幼稚園に通う4歳の娘がいます。
幼稚園は基本的に火・木がお弁当、金曜日は白米持参です。

ですが、イレギュラーが結構あり、都度紙で配布される園だよりによく見たら弁当日に変更する旨が書かれていて、情報が散らかっているため「どれが最新版?」「寝落ちして(←言い訳)見てなかった!」といったことが起きるような状態です。

弁当日以外もいろいろと忘れそうなイレギュラー対応があるので、他のお母さんたちの役にも立つかも?と思い、仕事とは全く関係ないところで失念防止対策をGASでしてみました。

できたもの

幼稚園から紙発信があったら、ブラウザからいつ何が必要等の登録をして、注意することがある前日の夜にLINE通知が来るように作りました。
GASはデプロイしたらサーバー用意しなくてもWebサービス公開できるというのをみたことがあったのでやってみました。
スプレッドシートをデータベースとして使用します。
本当に便利ですね!!


PC番登録画面


モバイル番登録画面


編集・削除もできます

ここで登録したことが登録されている日付の前日にLINE通知されます。

コード

作ってから結構時間がたっているので正直忘れかけていますが、、、
日付の操作はdays.js、CSSはbootstrapを使用しました。
bootstrapも知っていたけど使うのは初めてでしたが大変便利でした♪

form.html
<!DOCTYPE html>
<html>
  <head lang="ja">
    <base target="_top">
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js" integrity="XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" crossorigin="anonymous"></script>
    <script type="text/javascript">
      function disp(){
        // 「OK」時の処理開始 + 確認ダイアログの表示
        if(window.confirm('登録しますか?')){
          //FORMデータを送信する
          document.getElementById("form").submit(); 
        }else{
          window.alert('キャンセルされました'); // 警告ダイアログを表示
        }
      }

      function ShowDialog(value) {
      let dialog = document.querySelector('dialog');
        const rowNum = parseInt(value.value,10)+1;
        google.script.run.withSuccessHandler(
              function(resp){
                  console.log(resp);
                  let date = document.getElementById('dialog_date');
                  date.setAttribute('value', resp[0]);
                  let task = document.getElementById('dialog_text');
                  task.setAttribute('value', resp[1]);
                  let id = document.getElementById("rownum");
                  id.setAttribute('value', rowNum);
                  dialog.showModal();
              })
              .GetValue(rowNum);
      };

      function closeDialog() {
        let dialog = document.querySelector('dialog');
        dialog.close();
      };

      function update() {
        let date = document.getElementById("dialog_date").value;
        let task = document.getElementById("dialog_text").value;
        let id = document.getElementById("rownum").getAttribute('value');
        let del = document.getElementById("del");
        const rowValue = [id, date, task,del.checked];
        console.log(rowValue);
        google.script.run.withSuccessHandler(
              function(url){
                closeDialog();
                window.open(url, '_top');
              })
              .updateValue(rowValue);
      }
    </script>
  </head>
  <body>
    <div id="contents" class="container">
      <div class=”row”>
        <form id="form" action="https://script.google.com/macros/s/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" method="post">
          <h1>幼稚園タスク管理</h1>
          <p><?=result ?></p>
        <div class="item mb-3 col-">
          <label class="label form-label" for="date">日付</label>
          <input id="date" type="date" name="date" class="form-control">
        </div>
        <div class="item mb-3 col-">
          <label class="label form-label" for="value">内容</label>
          <input id="text" type="text" name="text" class="form-control">
        </div>
        <div class="item mb-3 form-check col-">
          <input type="checkbox" name="remind" checked>通知する
        </div>
          <input type="button" value="登録する" onClick="disp()" class="action btn btn-primary">
        </form>
      </div>
      <div class="lists">
          <h2>登録状況</h2>
          <?
            const tasklist = data;
            const today = dayjs.dayjs();
            output.append("<ul class='list-group list-group-flush'>");
            for(var i = 0; i < tasklist.length; i++) {
              if(today >= dayjs.dayjs(tasklist[i][0])) {
                continue;
              }
              var date = dayjs.dayjs(tasklist[i][0]).format("YYYY年MM月DD日");
              output.append("<li class='list-group-item' value='"+ i +"' onClick='ShowDialog(this)'>"+date+" : "+tasklist[i][1]+"</li>");
            }
            output.append("</ul>");
          ?>
      </div>
    </div>
    <dialog>
      <form id="dialog_form" action="https://script.google.com/macros/s/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX">
        <div class="item mb-3 col-">
          <input type="hidden" id="rownum">
          <label class="label form-label" for="date">日付</label>
          <input id="dialog_date" type="date" name="date" class="form-control">
        </div>
        <div class="item mb-3 col-">
          <label class="label form-label" for="value">内容</label>
          <input id="dialog_text" type="text" name="text" class="form-control">
        </div>
        <div class="item mb-3 form-check col-">
          <input type="checkbox" id="del">予定を削除する
        </div>
          <input id="register" type="button" value="登録する" class="action btn btn-primary" onclick="update()">
          <input id="cancel" type="button" value="キャンせル" class="action btn btn-primary" onClick='closeDialog()'>
        </form>
    </dialog>
   
  </body>
  <style>
    .selector-for-some-widget {
      box-sizing: content-box;
    }
    h1 {
      font-size: 40px;
      font-weight: bold;
      margin-bottom: 40px;
      text-align: center;
    }
    h2 {
      margin-top: 20px;
    }
    #form {
      margin: 0 auto;
    }
  </style>
</html>

コード.gs
//書き込み先スプレッドシートID
const gasheet = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";

function doGet() {
  let sheet = SpreadsheetApp.openById(gasheet).getSheetByName('POST');
  sheet.sort(1);
  let task = HtmlService.createTemplateFromFile('form');
  task.data = GetSpreadsheet();
  task.result="いつもと異なるスケジュール、お弁当日等、お気づきの際にご登録いただけると幸いです!";
  return task.evaluate().addMetaTag('viewport','width=device-width,initial-scale=1'); 
}
//スプレッドシートのデータを読み込む
function GetSpreadsheet(){
  //操作するスプレッドシートIDとシート名を指定して開く
  let sheet = SpreadsheetApp.openById(gasheet).getSheetByName('POST');
  
  //全データを取得するので、最終列と最終行を取得する
  let last_col = sheet.getLastColumn();  //最終列取得
  let last_row = sheet.getLastRow();     //最終行取得
  
  //データを取得する範囲を指定して取得し、2次元配列で返す
  return sheet.getRange(1, 1, last_row, last_col).getValues();
}

//POSTで受け取ってスプレッドシートへ書き込み
function doPost(e) {
  let ss = SpreadsheetApp.openById(gasheet);
  vlet sheet = ss.getSheetByName("POST");
  let array = [e.parameter.date,e.parameter.text,e.parameter.remind];
  sheet.appendRow(array);
  let task = HtmlService.createTemplateFromFile('thanks');
  task.data= GetSpreadsheet();
  return task.evaluate().addMetaTag('viewport', 'width=device-width, initial-scale=1');
}

function GetValue(rowNum){
  let sheet = SpreadsheetApp.openById(gasheet).getSheetByName('POST');
  let date = sheet.getRange(rowNum,1).getValue();
  let task = sheet.getRange(rowNum,2).getValue();
  var value = [dayjs.dayjs(date).format("YYYY-MM-DD"),task];
  return value;
}
function updateValue(arrayVal) {
  const rowNum = arrayVal[0];
  let sheet = SpreadsheetApp.openById(gasheet).getSheetByName('POST');
    if(!arrayVal[3]) {
      sheet.getRange(rowNum,2).setValue(arrayVal[2]);
      sheet.getRange(rowNum,1).setValue(arrayVal[1]);
    }else {
      //削除
      sheet.deleteRow(rowNum);
    }
  
 let url = ScriptApp.getService().getUrl();
  return url;
}
thanks.html
<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js" integrity="XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" crossorigin="anonymous"></script>
  </head>
  <body>
    <div class="container">
      <h1>登録ありがとうございます♪</h1>
      <div class="row">
        <a href="https://script.google.com/macros/s/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"><input type="button" value="登録画面へもどる" class="btn btn-primary"></a>
      </div>
      <div class="row">
        <h2>登録状況</h2>
        
            <?
              const tasklist = data;
              output.append("<ul class='list-group'>");
              for(var i = 0; i < tasklist.length; i++) {
                var date = Utilities.formatDate(tasklist[i][0], 'Asia/Tokyo', 'yyyy年MM月dd日');
                output.append("<li class='list-group-item'>"+date+" : "+tasklist[i][1]+"</li>");
              }
              output.append("</ul>");
            ?>
      </div>
    </div>
  </body>
  <style>
    .container {
      margin-top : 50px;
    }
    .row {
      margin-top: 20px;
    }
    h2 {
      margin-top: 20px;
    }
  </style>
</html>

解説

ザっといきます。
デプロイURL(後に解説)を開くとまず動くのはコード.jsのdoGet関数です。

function doGet() {
  let sheet = SpreadsheetApp.openById(gasheet).getSheetByName('POST');
  sheet.sort(1);
  let task = HtmlService.createTemplateFromFile('form');
  task.data = GetSpreadsheet();
  task.result="いつもと異なるスケジュール、お弁当日等、お気づきの際にご登録いただけると幸いです!";
  return task.evaluate().addMetaTag('viewport','width=device-width,initial-scale=1');
}

すでに登録されているスケジュールを画面上に表示したいですが、スプレッドシートのデータを日付順に並べてから表示した方がいいかと思い、その処理をまず行ってからフォームを表示しています。
form.htmlを呼び出す際に、「data」と「result」に値を設定しています。
また、モバイル対応するためにMetaTagも追加しています。

呼び出された先のform.htmlでは、下記の二か所で値を受け取って処理します。

<p><?=result ?></p>

単純に受け取った値を表示しているだけです。

<?
    const tasklist = data;
    const today = dayjs.dayjs();
    output.append("<ul class='list-group list-group-flush'>");
    for(var i = 0; i < tasklist.length; i++) {
      if(today >= dayjs.dayjs(tasklist[i][0])) {
	continue;
      }
      let date = dayjs.dayjs(tasklist[i][0]).format("YYYY年MM月DD日");
      output.append("<li class='list-group-item' value='"+ i +"' onClick='ShowDialog(this)'>"+date+" : "+tasklist[i][1]+"</li>");
    }
    output.append("</ul>");
?>

すでに過ぎ去った予定は表示する必要ないのでそのあたりを判別してから出力しています。

登録や更新の際にフォームの情報をスプレッドシートに登録するのが結構初心者にはわからないことが多くて大変でした。

更新の部分です。

let date = document.getElementById("dialog_date").value;
let task = document.getElementById("dialog_text").value;
let id = document.getElementById("rownum").getAttribute('value');
let del = document.getElementById("del");
const rowValue = [id, date, task,del.checked];
google.script.run.withSuccessHandler(
      function(url){
	closeDialog();
	window.open(url, '_top');
      })
      .updateValue(rowValue);
}

「google.script.run.withSuccessHandler」理解しないと難しいとおもいました。
コード.jsのDoPost関数を呼び出せます。
渡すときにeオブジェクトに渡したい値を設定してから呼び出しています。

//POSTで受け取ってスプレッドシートへ書き込み
function doPost(e) {
  var ss = SpreadsheetApp.openById(gasheet);
  var sheet = ss.getSheetByName("POST");
  var array = [e.parameter.date,e.parameter.text,e.parameter.remind];
  sheet.appendRow(array);
  var task = HtmlService.createTemplateFromFile('thanks');
  task.data= GetSpreadsheet();
  return task.evaluate().addMetaTag('viewport', 'width=device-width, initial-scale=1');
}

受け取ったeオブジェクトの中身を配列に入れて、そのままスプレッドシートの行追加しています。
DoPost関数の処理が終わったら呼び出しもとに戻りますが、非同期処理なのでコールバック関数で処理後の画面を表示するようにします。
行追加したら今度はthanks.htmlを表示します。
なにも設定しないとザ.デフォルト!みたいな画面が表示されたので、登録ありがとう的な画面を作らざるを得なかったと記憶しています。
thanks.htmlは過去のスケジュール全部表示されるようになっていたので、こちらも今日以降とかになるよう修正しなければと思っています・・!

その① おわりに

長くなるので編集画面のダイアログについてはまた後日記事を分けて投稿します。
少し時間がたってから見ると自分が書いたのもツッコミどころがたくさん見つかるので修正しつつ記事も更新していきたいです。

Discussion