Jesse
Jesse

Reputation: 1536

How to pass file upload blob from HTML form to server-side Apps Script?

The Google support article example under the Forms heading is broken. From the article:

If you call a server function with a form element as a parameter, the form becomes a single object with field names as keys and field values as values. The values are all converted to strings, except for the contents of file-input fields, which become Blob objects.

I tested this by passing a Form element containing 5 text inputs and a file, then logging Object.keys() on the form object. It returned only the 5 text fields, the file was stripped from the form object. Attempting to assign the file blob directly returned Exception: Invalid argument: blob

How do I pass the file blob from the client-side Form to the server-side Apps Script?

EDIT: To clarify, I also copy-pasted the example provided by Google verbatim. It errors with Exception: Invalid argument: blob.

To reproduce:

  1. Create new Google Apps Script project
  2. Index.html contents:
<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
    <script>
      // Prevent forms from submitting.
      function preventFormSubmit() {
        var forms = document.querySelectorAll('form');
        for (var i = 0; i < forms.length; i++) {
          forms[i].addEventListener('submit', function(event) {
            event.preventDefault();
          });
        }
      }
      window.addEventListener('load', preventFormSubmit);

      function handleFormSubmit(formObject) {
        google.script.run.withSuccessHandler(updateUrl).processForm(formObject);
      }
      function updateUrl(url) {
        var div = document.getElementById('output');
        div.innerHTML = '<a href="' + url + '">Got it!</a>';
      }
    </script>
  </head>
  <body>
    <form id="myForm" onsubmit="handleFormSubmit(this)">
      <input name="myFile" type="file" />
      <input type="submit" value="Submit" />
    </form>
    <div id="output"></div>
 </body>
</html>
  1. Code.gs contents:
function doGet() {
  return HtmlService.createHtmlOutputFromFile('Index');
}

function processForm(formObject) {
  var formBlob = formObject.myFile;
  var driveFile = DriveApp.createFile(formBlob);
  return driveFile.getUrl();
}
  1. Publish as Web App
  2. Submit the form with any file
  3. Observe error in View -> Stackdriver Logging -> Apps Script Dashboard

Upvotes: 0

Views: 2848

Answers (2)

IMTheNachoMan
IMTheNachoMan

Reputation: 5821

I can confirm that this doesn't work in G-Suite Enterprise. I don't know why because I cannot find documentation that says how Google is serializing the data. It could be a browser/computer security setting or something in G-Suite.

However, there is an easier way to accomplish your need. You can use a Google Form with a file upload question and then create an on form submit trigger/event on it to copy the file to a team/shared drive. Here is sample code if you want to attach the trigger to the Google Form itself:

// ID of the destnation folder to save the file in
var destinationFolderID = "10gkU_2V9iYy-VKudOCOjydEpoepPTgPv"

function saveFileToTeamDrive(e)
{
  // a place to save the URL of the uploaded file
  var fileID;

  // go through all of the responses to find the URL of the uploaded file
  e.response.getItemResponses().forEach(function(itemResponse){
    // once we find the question with the file
    if(itemResponse.getItem().getTitle() == "File Upload Test")
    {
      // get the file ID from the response
      fileID = itemResponse.getResponse();
      return;
    }
  });

  // stop if we didn't have one
  if(!fileID.length) return;

  // get the first index in the array
  fileID = fileID[0];

  // get the file
  var file = DriveApp.getFileById(fileID);

  // get the destination folder
  var destinationFolder = DriveApp.getFolderById(destinationFolderID);

  // make a copy
  var newFile = file.makeCopy(destinationFolder);

  Logger.log(newFile.getUrl());
}

You can also attach to the on form submit event of a Google Sheet that is linked to a Google Form. I find that way easier cause the Google Sheet on form submit trigger/event includes a JSON of the question/answers so you don't have to iterate all of them to find it. It also means you can re-run a submission if it failed.

WARNING

One important note, if you do either of these things do not give anyone else edit access to the code. This is because as soon as you create and authorize the trigger, anyone who has edit access to the code would be able to use it to gain access to your Google Drive (and anything else the trigger is authorized for). Please see securing a Google Apps Script linked to an authorized trigger so others can edit for more information.

Upvotes: 1

Cooper
Cooper

Reputation: 64082

Here's an example:

html:

<!DOCTYPE html>
<html>
  <head>
  <link rel="stylesheet" href="//code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css">
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
  <script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
  <script>
  function fileUploadJs(frmData) {
    document.getElementById('status').style.display ='inline';
    google.script.run
      .withSuccessHandler(updateOutput)
      .uploadTheFile(frmData)
  }

  function updateOutput(info)  {
    var br='<br />';
    var outputDiv = document.getElementById('status');
    outputDiv.innerHTML = br + 'File Upload Successful.' + br + 'File Name: ' + info.name + br + 'Content Type: ' + info.type + br + 'Folder Name: ' + info.folder;
  }

  console.log('My Code');
</script>
<style>
  body {background-color:#ffffff;}
  input{padding:2px;margin:2px;}
</style>

  </head>
  <body>
    <h1 id="main-heading">Walking Tracks</h1>
    <h3>Upload GPS Tracks Files</h3>
    <div id="formDiv">
      <form id="myForm">
        <input name="fileToLoad" type="file" /><br/>
        <input type="button" value="Submit" onclick="fileUploadJs(this.parentNode)" />
      </form>
    </div>
  <div id="status" style="display: none">
  <!-- div will be filled with innerHTML after form submission. -->
  Uploading. Please wait...
  </div>  
  <div id="controls">
      <input type="button" value="Close" onClick="google.script.host.close();" />
  </div>
</body>
</html>

server code:

function uploadTheFile(theForm) {
  var fileBlob=theForm.fileToLoad;
  var fldr = DriveApp.getFolderById('FolderId');
  var file=fldr.createFile(fileBlob);
  var fi=formatFileName(file);
  var fileInfo={'name':fi.getName(),'type':fileBlob.getContentType(), 'size':fileBlob.getBytes(), 'folder':fldr.getName()};
  return fileInfo;
}

Upvotes: 2

Related Questions