LoginSignup
0
2

個人的GASでWebアプリを作る時の初手行動

Posted at

ファイル構造

gsファイル2つと、htmlファイル3つを用意します。

mainにはdoGet,doPostの2つの関数を、それ以外の補助関数はfunctionsに放り込んでいます。

image.png

各ファイルの中身

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の分岐が多くなったら、処理ごとにファイルを分割します。

image.png

0
2
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
2