今日の目標:入力した内容をスプレッドシートに反映できる
0.前回の振り返り
画面遷移できるようになりました!
ちょっとした障害にドはまりしてました。
1.前回の課題「初期表示が遅い」への試行錯誤
各画面の初期表示が本当に重いのですが、スプレッドシートの検索は1回しかしていないので、検索回数を減らす以外の方法で高速化を目指します。
とりあえず、検索結果をキャッシュすれば早くなるのでは・・・?と思ったのでキャッシュしてみます。
// キャッシュ有効化
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になるようにしておきます。
/**
* 預かりさんの選択リスト一覧を作成する
*/
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;
}
<select class="detailvalueInput detailvalueSelect" value="<?= catHouse ?>">
<? output._ = selectOptions ?>
</select>
3.保存したらスプレッドシート更新処理が起動するように修正
ひとまずボタンをクリックしたら、.gs内のメソッドが起動するようにします。
保存後もアプリにとどまるので、素直にPOSTメソッドを利用します。
<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>
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属性を付与していきます。
<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]);
を使って、指定範囲のセルを値で完全に上書きしていきます。
/**
* 入力された値を更新する
*/
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);
を利用して、スプレッドシート内の最終行に新規追加していきます。
/**
* 体重、備考を追加する
* 体重: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. 保存時のローディングアニメーションを用意する
(前略)
<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>
(後略)
/* ローディングアニメーションのスタイル */
.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);
}
やっと、保存までこぎつけられました。
今日はここまで。