Skip to content
Google Apps Script + Spread Sheet = 微後端
📆2019-05-06 | 📂Google

若想以Google表單來做一個簡易的daily time tracking,提交一次表單就是一筆數據,但每天的時間總是會適當地分配,如此就得重複填寫並送出表單...這真是一個不聰明的方法,若自己寫的話因為某些原因難以避開CORS...就試著用Apps Script來解決這件事吧,我也沒寫過呢。


Create

Google Apps Script Doc : Web Apps

Requirements for web apps

A script can be published as a web app if it meets these requirements:

-It contains a doGet(e) or doPost(e) function. -The function returns an HTML service HtmlOutput object or a Content service TextOutput object.

首先建立Google雲端試算表和Apps Script,複製試算表網址“docs.google.com/spreadsheets/d/{Sheet ID}/edit#gid=0"當中的{Sheet ID},寫Apps Script的時候會需要這部分的資訊。

若找不到Apps Script,點選連結更多應用程式,過濾Google的應用程式就能看到了,然後就將它連結至個人的雲端硬碟。

建好表格欄位後就可以來寫App Script了

Apps Script

javascript
function doGet(e) {
  // parameter
  var params = e.parameter,
      date = params.Date,
      name = params.Name,
      proj = params.Proj,
      hours = params.Hours,
      des = params.Des;

  // google sheet
  var spreadSheet = SpreadsheetApp.openById({Sheet ID}),  // 需要Sheet ID
      sheet = spreadSheet.getSheets()[0],  // focus on sheet 1
      lastRow = sheet.getLastRow();
  
  // set value <= getRange(row, col)
  sheet.getRange(lastRow + 1, 1).setValue(date);
  sheet.getRange(lastRow + 1, 2).setValue(name);
  sheet.getRange(lastRow + 1, 3).setValue(proj);
  sheet.getRange(lastRow + 1, 4).setValue(hours);
  sheet.getRange(lastRow + 1, 5).setValue(des);

  // complete => return true
  return ContentService.createTextOutput(true);
}

app script寫好之後,可以另外寫個debug.gs來執行看看

javascript
function debug() {
  var status = doGet({
      "parameter": {
        "Date" : "2019-05-06",
        "Name" : "LaplaceTW",
        "Proj" : "Proj-19-05-06",
        "Hours" : "4",
        'Des' :"Daily Time Tracking",
      }
   });
  Logger.log("Status : %s" , status);
}

沒問題的話,就部署為網路應用程式,部署成功後可取得API網址

Test

用瀏覽器的開發人員模式console發送request測試

Passing an array into Google Apps Script ?

官方文件對參數的解釋是這樣的:

e.parameter An object of key/value pairs that correspond to the request parameters. Only the first value is returned for parameters that have multiple values. {"name": "alice", "n": "1"}

e.parameters An object similar to e.parameter, but with an array of values for each key {"name": ["alice"], "n": ["1", "2"]}

但實際測試的時候我也遇到如同Webduino官方教學文所說的,怎麼試就是無法透過e.parameters這個欄位傳遞參數呀!最後我也採用了相同的做法(汗),直接傳遞字串到Apps Script裡頭再分割數據。

javascript
// doGet(e)
var params = e.parameter,
    data = ['d1', 'd2', 'd3', 'd4', 'd5'],
    len = data.length,
    counter = 1.0;

while(counter <= len){
  var index = (counter - 1.0),
      arr = params[data[index]];
  if(arr != undefined){
    arr = arr.split(',');
    // set value <= getRange(row, col)
    sheet.getRange(lastRow + counter, 1).setValue(arr[0]);
    sheet.getRange(lastRow + counter, 2).setValue(arr[1]);
    sheet.getRange(lastRow + counter, 3).setValue(arr[2]);
    sheet.getRange(lastRow + counter, 4).setValue(arr[3]);
    sheet.getRange(lastRow + counter, 5).setValue(arr[4]);
  }
  counter ++;
}

Lock

在透過Ajax對Apps Script發送請求寫入Spread Sheet的測試過程中,原本前端會對User填寫的每筆數據個別送出Ajax請求,雖然過於頻繁並非好的處理方式,但這也才讓我意識到同時間多個寫入請求的衝突及數據覆寫問題,所以後來整合成一次性的Ajax請求,改為在Apps Script處理、分割數據的方式。但這樣還沒解決衝突問題,偉大的Google當然也有考量到潛在的寫入衝突問題:

Class Lock

A representation of a mutual-exclusion lock.

This class allows scripts to make sure that only one instance of the script is executing a given section of code at a time. This is particularly useful for callbacks and triggers, where a user action may cause changes to a shared resource and you want to ensure that aren't collisions.

藉由鎖定Script的方式,確保每個時刻只會有唯一的讀寫動作,來避免衝突問題。另外,SpreadsheetApp.flush()的使用也需特別留意。

javascript
// get a script lock for modifying a shared resource
var lock = LockService.getScriptLock();
lock.waitLock(30000);

// Do Something

// you should call SpreadsheetApp.flush() prior to releasing the lock,
// to commit all pending changes to the spreadsheet
SpreadsheetApp.flush();
lock.releaseLock();

Version

這問題耗了我整天的時間在測試...當Script更新並重新部署後,接下來卻發現,Ajax送出的請求一直抓不到參數,每個欄位都是呈現Undefined的狀態,但Debug.gs測試結果卻正常的很!!! 真是見鬼了...折騰半天我從測試結果合理推測部署的Web App所執行的不是最新版本,我在Stack Overflow也有找到相關討論:

建立一個Web App再另外建立Apps Script把相關邏輯寫成library,然後在Web App去呼叫它,這是一種方式,但我覺得以簡單的應用而言太拐彎抹角了。只要修改並善用debug測試,確認沒問題後再部署為一個新版本,如此外部呼叫就不會有上述的舊版本問題。

Last updated: