0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

5.入力した内容をスプレッドシートに保存できるようにする

Last updated at Posted at 2024-08-15

今日の目標:入力した内容をスプレッドシートに反映できる

0.前回の振り返り

画面遷移できるようになりました!

ちょっとした障害にドはまりしてました。

1.前回の課題「初期表示が遅い」への試行錯誤

各画面の初期表示が本当に重いのですが、スプレッドシートの検索は1回しかしていないので、検索回数を減らす以外の方法で高速化を目指します。
とりあえず、検索結果をキャッシュすれば早くなるのでは・・・?と思ったのでキャッシュしてみます。

List.gs
// キャッシュ有効化
const cache = makeCache();

function initList(e) {

  let htmlOutput;
  let cacheVal = cache.get('List');
  // キャッシュに値が残っているか確認
  if( !cacheVal && cacheVal !== null){
    htmlOutput = cacheVal;
  } else {
    // スプレッドシート検索
    const query = '=QUERY(🐈中略🐈)';
    const cats = getAllRecords(query);

    // 表示画面、変数定義
    const template = HtmlService.createTemplateFromFile('CatList');
    template.deployURL = ScriptApp.getService().getUrl();
    template.formHTML = setListHTML(e, cats, 'List');
    template.rows = rowNumCat;
    // html生成
    htmlOutput = template.evaluate();
    htmlOutput.setTitle('保護猫一覧');
    htmlOutput.setFaviconUrl(🐈中略🐈);
    htmlOutput.addMetaTag('viewport', 'width=device-width, initial-scale=1');

    // 検索結果をキャッシュに残す
    cache.put( 'List', htmlOutput );
  }
  return htmlOutput;
}

/**
 * キャッシュの保存・取得をする関数
 * 参考元:https://qiita.com/Terasan_Koshigaya/items/1144bc2f833f433416e0
 */
function makeCache() {
  const cache = CacheService.getScriptCache();
  return {
    get: function(key) {
      return JSON.parse(cache.get(key));
    },
    put: function(key, value) {
      //デフォルトでは10分間(600秒)保存される。最大値は6時間(21600秒)
      cache.put(key, JSON.stringify(value), 21600);
      return value;
    }
  };
}

スプレッドシートの検索結果のみをキャッシュするパターンと、検索結果をもって生成したhtmlをキャッシュするパターンで試したところ、htmlをキャッシュするほうが早さを実感できましたので、この形で行こうと思います。

2.選択リストをスプレッドシートの値から生成

入力画面で利用する一部選択肢の中身はスプレッドシートにあるので、まずはこれまでと同様に対象一覧を取得します。

ラベルは名前、ValueはIDになるようにしておきます。

CatDetailsEdit.gs
/**
 * 預かりさんの選択リスト一覧を作成する
 */
function setSelectOptions(e, template) {
  // 預かりさん一覧を取得
  const query = '=QUERY(' + CatHouseLines + ',"select * where A IS NOT NULL AND H = TRUE order by G, A", 1)';
  const person = getAllRecords(query);

  let html = ``;
  for(const man of person) {
    if( man['預かりさんID'] == template.personId ){
      html += `<option value="` + man['預かりさんID'] + `" selected>` + man['通称'] + `(` + man['預かり頭数'] + `)</option>`;
    } else {
      html += `<option value="` + man['預かりさんID'] + `">` + man['通称'] + `(` + man['預かり頭数'] + `)</option>`;
    }
  }
  template.selectOptions = html;

  return template;
}
edit.html
<select class="detailvalueInput detailvalueSelect" value="<?= catHouse ?>">
  <? output._ = selectOptions ?>
</select>

3.保存したらスプレッドシート更新処理が起動するように修正

ひとまずボタンをクリックしたら、.gs内のメソッドが起動するようにします。
保存後もアプリにとどまるので、素直にPOSTメソッドを利用します。

edit.html
  <form method="post" action="<?= deployURL ?>">
    <input type="hidden" name="type" value="edit"/>
    <input type="hidden" name="catId" value="<?= catId ?>"/>
    <h2 class="text-center m-4">保護猫詳細
      <button type="submit" class="catBtn btn btn-outline-primary">保存</button>
    </h2>
CatDetailsEdit.gs
function doPost(e) {

  // パラメータ取得
  const type = e.parameter.type;
  const catId = e.parameter.catId;
  const personId = e.parameter.personId;

  let htmlOutput;

  // 猫追加
  if( type === 'New' ){
    htmlOutput = doNew(e);
  }
  // 猫編集
  else if( type === 'Edit' ){
    htmlOutput = doEdit(e);
  }
  // 猫体重追加 OR 猫写真追加
  else if( type === 'Add-Weight' ||  type === 'Add-Photo' ){
    htmlOutput = doAdd(e, type);
  }

  return htmlOutput;
}

4.入力された内容でスプレッドシートの値を更新する

画面表示している内容と同じ行を更新していきます。
htmlの情値は、name要素をラベルとしてパラメータから取得するため、各種input,selectにはすべてname属性を付与していきます。

CatDetailsEdit.html
<div class="detail">
  <div class="detailLine">
    <span class="detailLabel">なまえ</span>
    <input type="text" name="catName" class="detailvalueInput" value="<?= catName ?>"/>
  </div>
</div>
<div class="detail">
  <div class="detailLine">
    <span class="detailLabel">性別</span>
    <span class="detailvalueRadio">
      <label><input type="radio" name="sexInput" value="♂" <?= catSexMale ?> > ♂ </label>
      <label><input type="radio" name="sexInput" value="♀" <?= catSexFemale ?> > ♀ </label>
    </span>
  </div>
</div>
<div class="detail">
  <div class="detailLine">
    <span class="detailLabel">預かりさん氏名</span>
    <select class="detailvalueInput detailvalueSelect" name="catHouse">
      <? output._ = selectOptions ?>
    </select>
  </div>
</div>
<div class="detail">
  <div class="detailLine">
    <span class="detailLabel">トライアル開始日</span>
    <input type="date" name="catTrialDate" class="detailvalueInput" value="<?= catTrialDate ?>"/>
  </div>
</div>

スプレッドシートにアクセスする回数が多ければ多いほど処理が遅くなるので、できるだけアクセス回数を減らすようにしています。
sheet.getRange(rangeArea).setValues([editCat]);を使って、指定範囲のセルを値で完全に上書きしていきます。

CatDetailsEdit.gs
/**
 * 入力された値を更新する
 */
function doEdit(e, catId) {

  // キャッシュのキーを生成
  EditCat = 'Edit' + catId;
  EditTemplateCat = 'EditTemplate' + catId;
  const DetailCat = 'Details' + catId;
  
  // スプレッドシート準備
  const ss = SpreadsheetApp.openByUrl(catMasterURL);
  const sheet = ss.getSheetByName('猫マスタ');
  // 猫IDは行番号と相対値になっているので、行を算出
  const rowNumber = parseInt(catId.replace('C','')) + 1;
  // CatMasterLines = "'猫マスタ'!A:AA";をもとに更新範囲を算出
  const rangeArea = "'猫マスタ'!A" + rowNumber + ":AA" + rowNumber;

  // 1行分の猫情報を作成し、更新する
  const editCat = setCatAllLines(e, rowNumber, catId, e.parameter.catURL);
  sheet.getRange(rangeArea).setValues([editCat]);

  // 変更前の値を取得
  beforeVal =  cache.get(EditTemplateCat);

  // 更新された値に応じてcacheクリア
  const keys = ['List', DetailCat, EditCat, EditTemplateCat];
  relatedCacheClear(keys);

  e.parameter.type = 'detail';
  e.parameter.catId = catId;
  const htmlOutput = initDetail(e, catId);
  return htmlOutput;
}

/**
 * 猫マスタ情報1行分全部設定
 */
function setCatAllLines(e, rowNumber, catId, url){
  cat = [
    catId,
    e.parameter.catName,
    e.parameter.sexInput,
    e.parameter.catBirthday,
    '=DATEDIF(D'+rowNumber+',TODAY(),"Y")&"歳"&DATEDIF(D'+rowNumber+',TODAY(),"YM")&"か月"&DATEDIF(D'+rowNumber+',TODAY(),"MD")&"日"',
    e.parameter.catProtectionDate,
    '=DATEDIF(F'+rowNumber+',TODAY(),"Y")&"年"&DATEDIF(F'+rowNumber+',TODAY(),"YM")&"か月"&DATEDIF(F'+rowNumber+',TODAY(),"MD")&"日"',
    e.parameter.catProtectionArea,
    e.parameter.catTrialDate,
    e.parameter.catTransferDate,
    e.parameter.catHouse,
    '=VLOOKUP(K'+rowNumber+",'預かりさん'!A:C,3,FALSE)",
    e.parameter.catVaccine1,
    e.parameter.catVaccine1Done,
    e.parameter.catVaccine2,
    e.parameter.catVaccine2Done,
    e.parameter.catSurgery,
    e.parameter.catSurgeryDone,
    e.parameter.catVirusCheckDate,
    e.parameter.catVirusCheckResultsInput,
    url,
    e.parameter.weight,
    e.parameter.trial,
    e.parameter.note,
    e.parameter.profile,
    e.parameter.catColor,
    '=VLOOKUP(Z'+rowNumber+",'猫柄リンク集'!A:B,2,FALSE)"
  ];
  return cat;
}

更新対象の1行の中には、数式で計算しておきたい列もあるので、数式を含めています。

5.入力された内容でスプレッドシートに行を追加

一部情報は更新履歴として別のスプレッドシートに行を追加していきます。
(最終的に体重のグラフを表示したいので、その準備🐈)
sheet.appendRow(catLog);を利用して、スプレッドシート内の最終行に新規追加していきます。

CatDetailsEdit.gs
/**
 * 体重、備考を追加する
 * 体重:weight:D列に保存
 * 備考:note:E列に保存
 */
function doAdd(e, type, catId) {

  EditCat = 'Edit' + catId;
  EditTemplateCat = 'EditTemplate' + catId;
  const DetailCat = 'Details' + catId;

  const ss = SpreadsheetApp.openByUrl(catLogURL);
  let sheet = ss.getSheetByName('猫備考系履歴');
  const now = Utilities.formatDate(new Date(), "JST", "yyyy/MM/dd HH:mm"); // 24h表記
  let catLog = [];
  // 体重
  if( type === 'Add-Weight' ){
    sheet = ss.getSheetByName('猫体重履歴');
    catLog = [catId, e.parameter.catName, now, e.parameter.weight];
  }
  // 備考
  else if( type === 'Add-Note' ){
    catLog = [catId, e.parameter.catName, now, '', e.parameter.note, ''];
  }
  // 一番最後の行に値を追加する
  sheet.appendRow(catLog);

  // 更新された値に応じてcacheクリア
  const keys = ['List', DetailCat, EditCat, EditTemplateCat];
  relatedCacheClear(keys);

  e.parameter.type = 'detail';
  e.parameter.catId = catId;
  const htmlOutput = initDetail(e, catId);
  return htmlOutput;
}

6. 保存時のローディングアニメーションを用意する

edit.html
(前略)
    <script>
      // loadingアニメーション表示
      function onClickBtn() {
        let loadingDiv = document.getElementById('loading');
        loadingDiv.style.display = 'block';
      }
    </script>
  </head>
  <body>
    <div id="loading" class="loading">
      <div class="ring">
        loading
        <span></span>
      </div>
    </div>
(後略)
css.html
  /* ローディングアニメーションのスタイル */
  .loading {
    display: none; /* 初期状態では非表示 */
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background: rgba(255, 255, 255, 0.8);
    z-index: 9999;
    text-align: center;
    padding-top: 20%;
  }
  .ring {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%,-50%);
    width:  150px;
    height: 150px;
    border: 3px solid #bd7ff8;
    border-radius: 50%;
    text-align: center;
    line-height: 150px;
    font-family: sans-serif;
    font-size: 20px;
    color: #8300ff;
    letter-spacing: 2px;
    text-transform: uppercase;
    background: #dedede;
    box-shadow: 0 0 0 4px #dedede;
  }
  .ring:before {
    content: '';
    position:absolute;
    top: 0px;
    left: 0px;
    height: 100%;
    width: 100%;
    border: 3px solid transparent;
    border-top: 3px solid #8300ff;
    border-right: 3px solid #8300ff;
    border-radius: 50%;
    animation: animateCircle 2s linear infinite;
  }
  .ring span {
    display: block;
    position: absolute;
    top: calc(50% - 2px);
    left: 50%;
    width: 50%;
    background: transparent;
    height: 4px;
    transform-origin: left;
    animation: animate 2s linear infinite;
  }
  .ring span:before {
    content: '';
    position: absolute;
    width: 16px;
    height: 16px;
    border-radius: 50%;
    background: #8300ff;
    top: -6px;
    right: -8px;
    box-shadow: 0 0 20px #8300ff;
  }
  @keyframes animateCircle {
    0% {
      transform: rotate(0deg);
    }
    100% {
      transform: rotate(360deg);
    }
  }

  @keyframes animate {
    0% {
      transform: rotate(45deg);
    }
    100% {
      transform: rotate(405deg);
    }

こんな感じの見た目になります。
image.png


やっと、保存までこぎつけられました。
今日はここまで。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?