透過 Google 表單將檔案上傳到 Google 雲端硬碟

程式設計程度:初學者
時間長度:10 分鐘
專案類型:使用事件驅動觸發條件的自動化動作

目標

  • 瞭解解決方案的功能。
  • 瞭解 Apps Script 服務在解決方案中的作用。
  • 設定指令碼。
  • 執行指令碼。

認識這項解決方案

使用 Google 表單同時上傳及整理 Google 雲端硬碟中的檔案。表單包含上傳檔案的輸入內容,以及檔案的整理方式。

上傳檔案表單的螢幕截圖

運作方式

設定函式會新增一個資料夾,儲存所有上傳的檔案,並建立一個觸發條件,在有人提交表單時啟動作業。使用者填寫表單時,可選擇要上傳的檔案,以及儲存這些檔案的子資料夾。表單上傳後,指令碼會將這些檔案轉存至相應的子資料夾。如果資料夾不存在,指令碼會自動建立新資料夾。

Apps Script 服務

本解決方案會使用下列服務:

  • 指令碼服務:建立觸發條件,在有人提交表單時啟動作業。
  • 資源服務:儲存指令碼在設定期間建立的觸發條件 ID,以免重複觸發條件。
  • 雲端硬碟服務:在設定期間,會取得表單在雲端硬碟中的儲存位置,並在相同位置建立資料夾。使用者提交表單後,雲端硬碟服務會將檔案轉存至該資料夾,如果有選取,則會轉存至指定的子資料夾。如果子資料夾不存在,指令碼會自動建立新資料夾。
  • 表單服務:取得使用者提交表單後所選取的檔案和資料夾名稱,並將其傳送至 Google 雲端硬碟服務。

必要條件

如要使用這個範例,您必須具備下列先決條件:

  • Google 帳戶 (Google Workspace 帳戶可能需要管理員核准)。
  • 可連上網際網路的網路瀏覽器。

設定指令碼

建立表單

  1. 前往 forms.google.com,然後按一下「空白」
  2. 按一下「未命名表單」,然後將表單重新命名為「將檔案上傳至雲端硬碟」
  3. 按一下「未命名的問題」,然後將問題重新命名為「子資料夾」
  4. 在「子資料夾」問題上,依序按一下「更多」圖示 >「說明」
  5. 在「說明」中輸入「選取要儲存檔案的子資料夾。如果選取「<None>」,檔案會儲存在「已上傳檔案」資料夾中。
  6. 在「子資料夾」問題中新增下列選項:
    • <none>
    • 專案 A
    • 專案 B
    • Project C
  7. 如要將問題設為必答,請按一下「必答」
  8. 按一下「新增問題」圖示
  9. 按一下「選擇題」,然後選取「檔案上傳」
  10. 按一下「繼續」
  11. 在「問題」部分,輸入「要上傳的檔案」。您可以選擇允許使用者上傳的檔案類型和檔案數量上限。
  12. 如要將問題設為必答,請按一下「必答」

建立 Apps Script 專案

  1. 在表單中,依序按一下「更多」圖示 >「指令碼編輯器」
  2. 按一下「Untitled project」,然後將專案重新命名為「Upload files to Drive」
  3. 如要建立其他指令碼檔案,請依序按一下「新增檔案」 >「指令碼」。將檔案命名為 Setup
  4. 將兩個指令碼檔案的內容替換成以下內容:

    Code.gs

    solutions/automations/upload-files/Code.js
    // TODO Before you start using this sample, you must run the setUp() 
    // function in the Setup.gs file.
    
    // Application constants
    const APP_TITLE = "Upload files to Drive from Forms";
    const APP_FOLDER_NAME = "Upload files to Drive (File responses)";
    
    // Identifies the subfolder form item
    const APP_SUBFOLDER_ITEM = "Subfolder";
    const APP_SUBFOLDER_NONE = "<None>";
    
    
    /**
     * Gets the file uploads from a form response and moves files to the corresponding subfolder.
     *  
     * @param {object} event - Form submit.
     */
    function onFormSubmit(e) {
      try {
        // Gets the application root folder.
        var destFolder = getFolder_(APP_FOLDER_NAME);
    
        // Gets all form responses.
        let itemResponses = e.response.getItemResponses();
    
        // Determines the subfolder to route the file to, if any.
        var subFolderName;
        let dest = itemResponses.filter((itemResponse) =>
          itemResponse.getItem().getTitle().toString() === APP_SUBFOLDER_ITEM);
    
        // Gets the destination subfolder name, but ignores if APP_SUBFOLDER_NONE was selected;
        if (dest.length > 0) {
          if (dest[0].getResponse() != APP_SUBFOLDER_NONE) {
            subFolderName = dest[0].getResponse();
          }
        }
        // Gets the subfolder or creates it if it doesn't exist.
        if (subFolderName != undefined) {
          destFolder = getSubFolder_(destFolder, subFolderName)
        }
        console.log(`Destination folder to use:
        Name: ${destFolder.getName()}
        ID: ${destFolder.getId()}
        URL: ${destFolder.getUrl()}`)
    
        // Gets the file upload response as an array to allow for multiple files.
        let fileUploads = itemResponses.filter((itemResponse) => itemResponse.getItem().getType().toString() === "FILE_UPLOAD")
          .map((itemResponse) => itemResponse.getResponse())
          .reduce((a, b) => [...a, ...b], []);
    
        // Moves the files to the destination folder.
        if (fileUploads.length > 0) {
          fileUploads.forEach((fileId) => {
            DriveApp.getFileById(fileId).moveTo(destFolder);
            console.log(`File Copied: ${fileId}`)
          });
        }
      }
      catch (err) {
        console.log(err);
      }
    }
    
    
    /**
     * Returns a Drive folder under the passed in objParentFolder parent
     * folder. Checks if folder of same name exists before creating, returning 
     * the existing folder or the newly created one if not found.
     *
     * @param {object} objParentFolder - Drive folder as an object.
     * @param {string} subFolderName - Name of subfolder to create/return.
     * @return {object} Drive folder
     */
    function getSubFolder_(objParentFolder, subFolderName) {
    
      // Iterates subfolders of parent folder to check if folder already exists.
      const subFolders = objParentFolder.getFolders();
      while (subFolders.hasNext()) {
        let folder = subFolders.next();
    
        // Returns the existing folder if found.
        if (folder.getName() === subFolderName) {
          return folder;
        }
      }
      // Creates a new folder if one doesn't already exist.
      return objParentFolder.createFolder(subFolderName)
        .setDescription(`Created by ${APP_TITLE} application to store uploaded Forms files.`);
    }

    Setup.gs

    solutions/automations/upload-files/Setup.js
    // TODO You must run the setUp() function before you start using this sample.
    
    /** 
     * The setUp() function performs the following:
     *  - Creates a Google Drive folder named by the APP_FOLDER_NAME
     *    variable in the Code.gs file.
     *  - Creates a trigger to handle onFormSubmit events.
     */
    function setUp() {
      // Ensures the root destination folder exists.
      const appFolder = getFolder_(APP_FOLDER_NAME);
      if (appFolder !== null) {
        console.log(`Application folder setup.
        Name: ${appFolder.getName()}
        ID: ${appFolder.getId()}
        URL: ${appFolder.getUrl()}`)
      }
      else {
        console.log(`Could not setup application folder.`)
      }
      // Calls the function that creates the Forms onSubmit trigger.
      installTrigger_();
    }
    
    /** 
     * Returns a folder to store uploaded files in the same location
     * in Drive where the form is located. First, it checks if the folder
     * already exists, and creates it if it doesn't.
     *
     * @param {string} folderName - Name of the Drive folder. 
     * @return {object} Google Drive Folder
     */
    function getFolder_(folderName) {
    
      // Gets the Drive folder where the form is located.
      const ssId = FormApp.getActiveForm().getId();
      const parentFolder = DriveApp.getFileById(ssId).getParents().next();
    
      // Iterates through the subfolders to check if folder already exists.
      // The script checks for the folder name specified in the APP_FOLDER_NAME variable.
      const subFolders = parentFolder.getFolders();
      while (subFolders.hasNext()) {
        let folder = subFolders.next();
    
        // Returns the existing folder if found.
        if (folder.getName() === folderName) {
          return folder;
        }
      }
      // Creates a new folder if one doesn't already exist.
      return parentFolder.createFolder(folderName)
        .setDescription(`Created by ${APP_TITLE} application to store uploaded files.`);
    }
    
    /**
     * Installs trigger to capture onFormSubmit event when a form is submitted.
     * Ensures that the trigger is only installed once.
     * Called by setup().
     */
    function installTrigger_() {
      // Ensures existing trigger doesn't already exist.
      let propTriggerId = PropertiesService.getScriptProperties().getProperty('triggerUniqueId')
      if (propTriggerId !== null) {
        const triggers = ScriptApp.getProjectTriggers();
        for (let t in triggers) {
          if (triggers[t].getUniqueId() === propTriggerId) {
            console.log(`Trigger with the following unique ID already exists: ${propTriggerId}`);
            return;
          }
        }
      }
      // Creates the trigger if one doesn't exist.
      let triggerUniqueId = ScriptApp.newTrigger('onFormSubmit')
        .forForm(FormApp.getActiveForm())
        .onFormSubmit()
        .create()
        .getUniqueId();
      PropertiesService.getScriptProperties().setProperty('triggerUniqueId', triggerUniqueId);
      console.log(`Trigger with the following unique ID was created: ${triggerUniqueId}`);
    }
    
    /**
     * Removes all script properties and triggers for the project.
     * Use primarily to test setup routines.
     */
    function removeTriggersAndScriptProperties() {
      PropertiesService.getScriptProperties().deleteAllProperties();
      // Removes all triggers associated with project.
      const triggers = ScriptApp.getProjectTriggers();
      for (let t in triggers) {
        ScriptApp.deleteTrigger(triggers[t]);
      }
    }
    
    /**
     * Removes all form responses to reset the form.
     */
    function deleteAllResponses() {
      FormApp.getActiveForm().deleteAllResponses();
    }

執行指令碼

  1. 在 Apps Script 編輯器中,切換至 Setup.gs 檔案。
  2. 在函式下拉式選單中,選取 setUp
  3. 按一下「執行」
  4. 出現提示時,請授權執行指令碼。如果 OAuth 同意畫面顯示「This app isn't verified」警告,請依序選取「Advanced」「Go to {Project Name} (unsafe)」(前往「{Project Name}」(不安全))。

  5. 返回表單,然後按一下「預覽」圖示 預覽圖示

  6. 在表單中選取子資料夾,然後上傳檔案。

  7. 按一下「提交」

  8. 前往雲端硬碟,然後開啟「將檔案上傳至雲端硬碟 (檔案回覆)」資料夾。上傳的檔案會儲存在表單中選取的子資料夾中。

貢獻者

這個範例是由 Google 維護,並由 Google 開發人員專家提供協助。

後續步驟