ファイル構造
gsファイル2つと、htmlファイル3つを用意します。
mainにはdoGet,doPostの2つの関数を、それ以外の補助関数はfunctionsに放り込んでいます。
各ファイルの中身
main.gs
doGet,doPostの2つのみを記載するようにしています。eventHandlerについては後述します。
function doGet(e){
}
function doPost(e){
const content = JSON.parse(e.postData.content);
}
const eventHandler = {};
functions.gs
補助で使用する関数を記載しています。
デフォルトでは以下の関数を入れるようにしています。
① gssConsole
GASのWebアプリではconsoleが表示されないことがあるので、console.logの代わりに使用しています。
gssConsole.setSheetでログを吐き出すシートを指定した後、gssConsole.log()を使います。
配列やオブジェクトもすべて文字列に変換してから出力します。
② returnJSON
JSON形式で返すときに、いちいち書くのがめんどくさいので入れています。
③ env
PropertiesServiceの記述が面倒なので入れています。
//通常
PropertiesService.getScriptProperties().getProperty("hoge");
//env ver
env.hoge;
④Cache
envと同様の理由です。CacheServiceの記述が面倒なので入れました。
詳しくはこちら
const gssConsole = {
setSheet(sheet){
this.sheet = sheet;
},
log(...content){
this.sheet.appendRow(content.map(v => JSON.stringify(v)));
}
}
function retrunJSON(content){
return ContentService.createTextOutput().setContent(JSON.stringify(content)).setMimeType(ContentService.MimeType.JSON);
}
const env = (() => new Proxy({e : PropertiesService.getScriptProperties()}, {
get: (t, p) => t.e ? t.e.getProperty(p) : void 0,
set: (t, k, v) => t.e ? t.e.setProperty(k, v) : void 0
}))();
const Cache = (function(){
const ScriptCache = CacheService.getScriptCache();
const parseJSON = str => {
try{
return JSON.parse(str);
}catch{
return str;
}
};
return {
/**
* @return {array} キャッシュに登録されている全てのキーを取得します。[key1,key2,key3,...]
*/
getKeys(){
return this.require("ALL_KEYS_OF_THIS");
},
/**
* キーからデータを取得。
* @param {string} 取得したいデータのキー。(省略可)
* @param {array} 取得したいデータのキー一覧。(省略可)
* @return {object} キーに対応するデータ。省略した場合は、キャッシュされている全てのデータ。
*/
require(key){
let all;
if(typeof key === "string"){
return parseJSON(ScriptCache.get(key));
}else if(Array.isArray(key)){
all = ScriptCache.getAll(key);
}else if(!key){
all = ScriptCache.getAll(this.getKeys());
}
const obj = {};
for(const key in all){
obj[key] = parseJSON(all[key]);
}
return obj;
},
/**
* データを追加。
*
* @param {string} key キー名
* @param {object} key {key,value}の形にすること。
* @param {} value 型はなんでも良い。(省略可)
*/
exports(key,value){
//""をキー名として許可すると、this.require()で区別がつかない。
if(!key) throw new Error("第一引数は必須です。");
if(key === "ALL_KEYS_OF_THIS") throw new Error("禁止ワードです。");
//日付をJSONに変換する時の処理。デフォルトだと扱いにくいので上書き。
Date.prototype.toJSON = function(){
return Utilities.formatDate(this,"JST","yyyy-MM-dd hh:mm:ss");
}
if(typeof key === "string"){
const keys = this.require("ALL_KEYS_OF_THIS") || [];
ScriptCache.put(key,JSON.stringify(value),21600);
keys.push(key);
ScriptCache.put("ALL_KEYS_OF_THIS",JSON.stringify(keys),21600);
}else if(typeof key === "object"){
const keys = this.require("ALL_KEYS_OF_THIS") || [];
for (let i in key){
key[i] = JSON.stringify(key[i]);
keys.push(i);
}
ScriptCache.put("ALL_KEYS_OF_THIS",JSON.stringify(keys),21600)
ScriptCache.putAll(key,21600);
}
return this;
},
/**
* データの削除。
* @param {string} key 削除したいデータのキー名(省略可)
* @param {array} key 削除したいデータの全てのキー名(省略可)
* @param {} key 省略した場合、全てのデータを削除。
*/
remove(key){
if(typeof key==="string"){
ScriptCache.remove(key);
const keys = this.require("ALL_KEYS_OF_THIS") || [];
this.exports("ALL_KEYS_OF_THIS",keys.filter(k => k!==key));
}else if(Array.isArray(key)){
ScriptCache.removeAll(key);
const keys = this.require("ALL_KEYS_OF_THIS") || [];
this.exports("ALL_KEYS_OF_THIS",keys.filter(k => !keys.includes(key)));
}else if(!key){
ScriptCache.removeAll(this.getKeys());
this.exports("ALL_KEYS_OF_THIS",[]);
}
return this;
}
}
})();
index.html
GASの仕様上、script.js,style.cssと分けることができないので、すべてhtmlで書いたのちに、GASの機能で読み込んでいます。
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<?!= HtmlService.createHtmlOutputFromFile('style').getContent(); ?>
</head>
<body>
<?!= HtmlService.createHtmlOutputFromFile('script').getContent(); ?>
</body>
</html>
script.gs
ここにjavascriptをいろいろ書きます。
google.script.runの処理を書くのが面倒なので、async/awaitで記載できるようにしています。
<script>
//ajax
const _gas = new Proxy({},{
get(t,p){
return (...a) => new Promise((r1,r2) => {
google.script.run.withSuccessHandler(r1)
.withFailureHandler(r2)
[p](...a);
})
}
});
</script>
style.css
ここにcssを書きます。
<style>
</style>
条件分岐について
doGet/doPostのパラメータに処理内容を入れる場合があると思いますが、
できる限りif文/switch文を使わないようにしています。
function doGet(e){
const { type } = e.parameter;
eventHandler[type](e); //if(type === "reply"){}とは書かない
}
const eventHandler = {};
eventHandler.reply = function(e){
//処理
};
typeの分岐が多くなったら、処理ごとにファイルを分割します。