読者です 読者をやめる 読者になる 読者になる

技術のメモ帳

気が向いたときに書いてます

[GAS]Googleフォームを経由せず、外部からデータを更新する

お問い合わせやフィードバック等を受け付ける場合、Googleフォームで手軽に受け入れ先を作成できますが、UIのカスタマイズに制限があり、また、外部リンクとなるため、少し不便な部分もあります。

本エントリでは、外部サイトからPOST送信し、SpreadSheetを更新するスクリプトを書いていきます。

POSTリクエストを受け付ける

まず、新規プロジェクト(空のプロジェクト)を作成し、POSTリクエストを受け付ける関数を作成します。

function doPost() {
  return ContentService.createTextOutput('post request');
}

関数を記述したら、[公開]→[ウェブアプリケーションとして導入]を選び、[アプリケーションにアクセスできるユーザー]は[全員(匿名ユーザーを含む)]を選択します。

これで、ウェブアプリケーション用途のURLが発行されます。

POSTリクエストを送信する

curlコマンドを使って、発行URLに対してリクエストします。

$ curl -d foo=bar -L https://script.google.com/macros/s/***ID***/exec

post requestという文字列が返ることが確認できます。

JSONを返す

フォームの仕様としては、外部サイトのフォームの入力内容を、javascriptを使って送受信することを想定しています。要するに、Google Apps Scriptを用いて、SpreadSheetを外部APIとして機能させるわけです。

JSONを返す場合は、JSON.stringify()というメソッドを使います。

まずは、JSONを返す関数を作成し、動作確認します。

function jsonResponse() {
  var res = { result: "success" };
  Logger.log(JSON.stringify(res));
}

問題なく、JSONオブジェクトが返されています。

HTTPで返す場合は、JSONのMimeTypeも追加する必要があるため、doPost関数は次のように変更します。

function doPost() {
  var res = { result: "success" };
  return ContentService.createTextOutput(JSON.stringify(res)).setMimeType(ContentService.MimeType.JSON);
}

先ほどと同じく、curlコマンドを実行すると、{"result":"success"}が返ることが確認できます。

こちらを応用すると、簡易的なAPIやモックを作成することも可能です。

リクエストパラメータを受け付ける

POSTリクエストと共に送られるパラメータを処理するには、doPost関数に引数を追加します。

function doPost(e) {
  var res = e.parameter;
  return ContentService.createTextOutput(JSON.stringify(res)).setMimeType(ContentService.MimeType.JSON);
}

curlコマンドで、message=foobarを追加して実行すると、{"message":"foobar"}が返されることが確認できます。

SpreadSheetの準備

送信情報を記録するためのSpreadSheetを用意します。

フォームから受け付ける値は、メールアドレスとメッセージを想定します。

function createSpreadSheet() {
  var props = PropertiesService.getScriptProperties();
  if (props.getProperty('WEBFORM_BOOK_ID') === null) {
    var book   = SpreadsheetApp.create('フォーム回答'),
        header = ['email', 'message', 'created_at'];
    props.setProperty('WEBFORM_BOOK_ID', book.getId());
    book.getActiveSheet().getRange(1, 1, 1, header.length).setValues([header]);
  }
}

POST送信をハンドリングする

doPost関数の実装に戻ります。HTMLフォームから送信された情報をSpreadSheetに登録します。

なお、サンプルのため、それぞれの値のバリデーションは割愛しています。

function doPost(e) {
  if (!e) return;
  var res = requestHandler(e.parameter);
  return ContentService.createTextOutput(JSON.stringify(res)).setMimeType(ContentService.MimeType.JSON);
}

function requestHandler(params) {
  var email   = params.email,
      message = params.message;
  if (updateSpreadSheet(email, message)) {
    return { "result": "success" };
  } else {
    return { "result": "error" };
  }
}

function updateSpreadSheet(email, message) {
  try {
    var book  = SpreadsheetApp.openById(PropertiesService.getScriptProperties().getProperty('WEBFORM_BOOK_ID')),
        sheet = book.getActiveSheet(),
        dt    = new Date();
    sheet.appendRow([email, message, dt.toLocaleString()]);
    return true;
  } catch(e) {
    Logger.log(e);
    return false;
  }
}

クライアントの実装

ここからは、クライアント(HTML/JS)の実装をしていきます。

なお、サンプルのため、defferedなどの実装は割愛しています。

<form method="post" action="https://script.google.com/macros/s/AKfycbzMfUImVYMoJzJVNVC3hxNKffzjwMZUO-_LLgb8KcqDZ5V6X4BF/exec">
  <div>
    <label for="email">Email</label>
    <input name="email" id="email" type="text" />
  </div>
  <div>
    <label for="message">Message</label>
    <textarea name="message" id="message" row="3"></textarea>
  </div>
  <input type="submit" />
</form>

JSはCoffeeScriptで書きました。

$ ->
  $('form').on 'submit', (e) ->
    e.preventDefault()
    form_url  = $(this).attr("action")
    form_type = $(this).attr("method").toUpperCase()
    form_data = $(this).serialize()
    $.ajax
      url: form_url
      type: form_type
      data: form_data
        dataType: 'json'
        crossDomain: true
        cache: false

動作確認

ウェブフォーム送信後、次のように表示されました。

ウェブアプリケーションとして動作させる場合は、コードを変更する度に、バージョンを更新する必要があるのでご注意ください。

最後に今回書いた、form.gsをすべて載せておきます。

function doPost(e) {
  if (!e) return;
  var res = requestHandler(e.parameter);
  return ContentService.createTextOutput(JSON.stringify(res)).setMimeType(ContentService.MimeType.JSON);
}

function requestHandler(params) {
  var email   = params.email,
      message = params.message;
  if (updateSpreadSheet(email, message)) {
    return { "result": "success" };
  } else {
    return { "result": "error" };
  }
}

function updateSpreadSheet(email, message) {
  try {
    var book  = SpreadsheetApp.openById(PropertiesService.getScriptProperties().getProperty('WEBFORM_BOOK_ID')),
        sheet = book.getActiveSheet(),
        dt    = new Date();
    sheet.appendRow([email, message, dt.toLocaleString()]);
    return true;
  } catch(e) {
    Logger.log(e);
    return false;
  }
}

function createSpreadSheet() {
  var props = PropertiesService.getScriptProperties();
  if (props.getProperty('WEBFORM_BOOK_ID') === null) {
    var book   = SpreadsheetApp.create('フォーム回答'),
        header = ['email', 'message', 'created_at'];
    props.setProperty('WEBFORM_BOOK_ID', book.getId());
    book.getActiveSheet().getRange(1, 1, 1, header.length).setValues([header]);
  }
}