🐺

Googleフォーム / GASのフォームで登録されたデータでPDFを作成する

2023/10/17に公開

前回GASでフォームを作るときに知りたかったことをまとめました。
https://zenn.dev/mazumazu/articles/cc699a6fb6e367

今回はそのデータを元に帳票(PDF)出力します。

処理の流れ

  1. POSTのパラメータを取得する。 (doPost)
  2. 帳票テンプレートからシートをコピーする (copyTemplateSheet)
  3. コピーしたシートにデータを書き込む (writeSheet)
  4. 一旦画面に処理を返す (doPost終了)
  5. ブラウザの DOMContentLoaded イベントでpdf作成&ダウンロード (pdf.html)
  6. スプレッドシートIDとシート名を元に3で出力したシートを取得 (loadPDF)
  7. 6で取得したシートをPDFに変換する (makePDF)
  8. 画面へPDFをdataURL形式で返す (loadPDF)
  9. dataURLをaタグに設定して自動クリックでダウンロードする (pdf.html)

注意点

シートにデータ書き込み後(3の後)にそのままPDFを作成するとテンプレートのままのPDFが出力されます。
データ反映がされないままpdf作成されるため起こるようで、Utilities.sleep(1000)を使う等、サーバ側で待っても更新されなかったため一旦ブラウザに処理を返しています。

ソースコード

サーバ側処理

server.gs
/** テンプレートのスプレッドシートID */
const TEMPLATE_SHEET_SPREADSHEET_ID = 'XXXXXXXXXXXXXXXXXXXXXXXXXXX';
/** テンプレートのシート名 */
const TEMPLATE_SHEET_NAME = 'YYYYYY';
/** 出力先フォルダID */
const OUTPUT_FOLDER_ID = 'ZZZZZZZZZZZZ';

/**
 * POST処理をする
 */
function doPost(e) {
  // formで登録されたデータを取得する
  const params = e.parameters;
  const addressList = params.address;
  const name = 'something';
  // テンプレートから出力シート作成
  const sheet = copyTemplateSheet(name);
  // 書き込み
  writeSheet(sheet, addressList);
  
  // HTMLを出力
  const template = HtmlService.createTemplateFromFile('pdf');
  template.id = sheet.getParent().getId();
  template.name = name;
  return template.evaluate();
}

/**
 * テンプレートシートを作成する
 * @param {string} name
 * @returns {SpreadsheetApp.Sheet}
 */
function copyTemplateSheet(name) {
  const templateSS = SpreadsheetApp.openById(TEMPLATE_SHEET_SPREADSHEET_ID);
  const template = templateSS.getSheetByName(TEMPLATE_SHEET_NAME);
  const folder = DriveApp.getFolderById(OUTPUT_FOLDER_ID);
  const outputSS = SpreadsheetApp.create(name);
  const ssId = outputSS.getId();
  DriveApp.getFileById(ssId).moveTo(folder);

  const outputSheet = template.copyTo(outputSS);
  outputSheet.setName(name);
  return outputSheet;
}

/**
 * シートを更新する
 * @param {SpreadsheetApp.Sheet} sheet
 * @param {string[]} addressList
 */
function writeSheet(sheet, addressList) {
  // シートを更新する
}

/**
 * ファイルの親フォルダを確認する
 * @param {string} id スプレッドシートID
 */
function checkParent(id) {
  const file = DriveApp.getFileById(id);
  const folder = file.getParents().next();
  if (folder.getId() !== OUTPUT_FOLDER_ID)
    throw new Error('対象外のスプレッドシートが指定されました。');
}

/**
 * pdfを作成し、返す
 * @param {string} id スプレッドシートID
 * @param {string} name シート名
 */
function loadPDF(id, name) {
  const ss = SpreadsheetApp.openById(id);
  checkParent(ss.getId());
  const folder = DriveApp.getFolderById(OUTPUT_FOLDER_ID);
  
  const sheet = ss.getSheetByName(name);
  const file = makePDF(sheet, folder);
  return {
    url: `data:${file.getMimeType()};base64,${Utilities.base64Encode(file.getBlob().getBytes())}`,
    filename: file.getName(),
  };
}

/**
 * 特定のシートをPDF化する
 * @param {SpreadsheetApp.Sheet} sheet 出力対象シート
 * @param {DriveApp.Folder} folder 保存先フォルダ
 * @return {DriveApp.File} ドライブのファイル
 */
function makePDF(sheet, folder) {
  const ss = sheet.getParent();
  const id = ss.getId();
  const sheetID = sheet.getSheetId();

  // URLの組み立て 縦、A4、グリッド線非表示、幅に合わせる を設定している
  const url = `https://docs.google.com/spreadsheets/d/${id}/export?gid=${sheetID}&format=pdf&portrait=true&size=A4&gridlines=false&fitw=true`;
  
  // PDFをfetchする
  const token = ScriptApp.getOAuthToken();
  const headers = {
    headers: {'Authorization': `Bearer ${token}`}
  }
  const pdf = UrlFetchApp.fetch(url, headers).getBlob();
  const name = `${ss.getName()}.pdf`;
  pdf.setName(name);
  return folder.createFile(pdf);
}

HTML

pdf.html
<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
  </head>
  <body>
    <div id="message">PDF準備中です。</div>
  </body>
  <script>
    document.addEventListener('DOMContentLoaded', function () {
      google.script.run
        .withSuccessHandler(({ url, filename }) => {
          if (url && filename) {
            const a = document.createElement('a');
            document.body.appendChild(a);
            a.innerHTML = 'PDFダウンロード';
            a.download = filename;
            a.href = url;
            a.click();
            document.querySelector('#message').innerHTML = '帳票をダウンロードしました。ダウンロードフォルダをご確認ください。';
          } else {
            document.querySelector('#message').innerHTML = 'ダウンロードに失敗しました。';
          }
        })
        .loadPDF('<?= id ?>', '<?= name ?>');
	// id: スプレッドシートID, name: 出力シート名
    });
  </script>
</html>

誰かの参考になれば幸いです。

Discussion