Form and File Upload with HTMLservice and App Script Not Working

Form and file upload with htmlService and app script not working

Edit: Working example

The HtmlService does not support the post method for HTML forms. The input elements collected by a form can be communicated to a server-side function using a handler function instead. For more details, see HTML Service: Communicate with Server Functions.

Here's an example based on the code you've posted in your question.

Form.html

<script>
// Javascript function called by "submit" button handler,
// to show results.
function updateOutput(resultHtml) {
toggle_visibility('inProgress');
var outputDiv = document.getElementById('output');
outputDiv.innerHTML = resultHtml;
}

// From blog.movalog.com/a/javascript-toggle-visibility/
function toggle_visibility(id) {
var e = document.getElementById(id);
if(e.style.display == 'block')
e.style.display = 'none';
else
e.style.display = 'block';
}
</script>

<div id="formDiv">
<!-- Form div will be hidden after form submission -->
<form id="myForm">

Name: <input name="name" type="text" /><br/>
Department: <select name="department">
<option>Select Option</option>
<option>Cashier</option>
<option>Greeter</option>
<option>Runner</option>
<option>Line Control</option>
<option>IDB</option>
<option>Unknown</option>
</select><br/>
Email: <input name="email" type="text" /><br/>
Message: <textarea name="message" style="margin: 2px; height: 148px; width: 354px;"></textarea><br/>
School Schedule (Image Files Only): <input name="myFile" type="file" /><br/>
<input type="button" value="Submit"
onclick="toggle_visibility('formDiv'); toggle_visibility('inProgress');
google.script.run
.withSuccessHandler(updateOutput)
.processForm(this.parentNode)" />
</form>
</div>

<div id="inProgress" style="display: none;">
<!-- Progress starts hidden, but will be shown after form submission. -->
Uploading. Please wait...
</div>

<div id="output">
<!-- Blank div will be filled with "Thanks.html" after form submission. -->
</div>

Thanks.html

<div>
<h1>Thanks</h1>
<p>Thank you for your submission.</p>
Name: <?= name ?><br/>
Department: <?= department ?><br/>
Message: <?= message ?><br/>
Email: <?= email ?><br/>
File URL: <?= fileUrl ?><br/>
</div>

Code.gs

var submissionSSKey = '--Spreadsheet-key--';
var folderId = "--Folder-Id--";

function doGet(e) {
var template = HtmlService.createTemplateFromFile('Form.html');
template.action = ScriptApp.getService().getUrl();
return template.evaluate();
}


function processForm(theForm) {
var fileBlob = theForm.myFile;
var folder = DriveApp.getFolderById(folderId);
var doc = folder.createFile(fileBlob);

// Fill in response template
var template = HtmlService.createTemplateFromFile('Thanks.html');
var name = template.name = theForm.name;
var department = template.department = theForm.department;
var message = template.message = theForm.message;
var email = template.email = theForm.email;
var fileUrl = template.fileUrl = doc.getUrl();

// Record submission in spreadsheet
var sheet = SpreadsheetApp.openById(submissionSSKey).getSheets()[0];
var lastRow = sheet.getLastRow();
var targetRange = sheet.getRange(lastRow+1, 1, 1, 5).setValues([[name,department,message,email,fileUrl]]);

// Return HTML text for display in page.
return template.evaluate().getContent();
}

Original answer, which was focused on basic debugging:

Where does this code come from originally? There have been multiple questions about it, and it might be helpful to see the original tutorial or example it was taken from.

When you run this code as a published web app, and submit a file, the error you receive is TypeError: Cannot read property "thefile" from undefined. Without any more digging, this tells you that there's an undefined object being used in your code. What object is that? Don't know yet, but a clue is that the code is looking for a property named "thefile".

If you have the script open in the editor, and launched the webapp from there (by clicking on Test web app for your latest code in the Publish / Deploy as Web App dialog), then you can also check the Execution Transcript for more details. (under View menu) You'll find it contains something like this:

[13-12-25 07:49:12:447 EST] Starting execution
[13-12-25 07:49:12:467 EST] HtmlService.createTemplateFromFile([Thanks.html]) [0 seconds]
[13-12-25 07:49:12:556 EST] SpreadsheetApp.openById([--SSID--]) [0.089 seconds]
[13-12-25 07:49:12:557 EST] Spreadsheet.getSheets() [0 seconds]
[13-12-25 07:49:12:626 EST] Sheet.getLastRow() [0.067 seconds]
[13-12-25 07:49:12:627 EST] Sheet.getRange([1, 1, 1, 5]) [0 seconds]
[13-12-25 07:49:12:629 EST] Range.setValues([[[N/A, , Select Option, , ]]]) [0.001 seconds]
[13-12-25 07:49:12:983 EST] Execution failed: TypeError: Cannot read property "thefile" from undefined. (line 20, file "Code") [0.17 seconds total runtime]

We see that same error, but now we know the line number. That line contains a spelling mistake:

var fileBlob = e.paramater.thefile
^^^^^^^^^

Uploading a file using an HtmlService form in Google Apps always causes server error and a stack trace

It appears that the issue only occurs in a recently released version of Google App's scripting interface, "V8".

When I create a script, I am prompted to use this version of their scripting interface. Trusting Google to test their functionalities, I accepted without thinking.

If I edit my script's configuration file to use STABLE instead of V8, I do not encounter the issue. If you're having this issue, here's how to do that:

  1. Open the Script Editor.
  2. In the top menu, select View > Show manifest file.
  3. In the files list, open appsscript.json.
  4. Replace "runtimeVersion": "V8" with "runtimeVersion": "STABLE"
  5. Save.

This is however alarming as I presume the current stable version will be deprecated eventually in favor of V8. I logged an issue for this: https://issuetracker.google.com/issues/149980602

Uploading file using Google Apps Script using HtmlService

Have the button run the server side function using google.script.run, passing in the entire form as the only parameter. (Inside the button's onClick, 'this' is the button, so 'this.parentNode' is the form.) Make sure to give the file input a name.

<html>
<body>
<form>
<input type="file" name="theFile">
<input type="hidden" name="anExample">
<input type="button" onclick="google.script.run.serverFunc(this.parentNode)">
</form>
</body>
</html>

On the server, have your form handling function take one parameter - the form itself. The HTML form from the client code will be transformed into an equivalent JavaScript object where all named fields are string properties, except for files which will be blobs.

function doGet() {
return HtmlService.createHtmlOutputFromFile('myPage');
}

function serverFunc(theForm) {
var anExampleText = theForm.anExample; // This is a string
var fileBlob = theForm.theFile; // This is a Blob.
var adoc = DocsList.createFile(fileBlob);
return adoc.getUrl();
}

If you actually want to use that URL you are generating and returning, be sure to add a success handler to the google.script call. You can modify it like this:

// Defined somewhere before the form
function handler(url) {
// Do something with the url.
}

<input type="button" onclick=
"google.script.run.withSuccessHandler(handler).serverFunc(this.parentNode)">

Moving google apps script to v8 file upload stopped working from sidebar

How about this answer? Please think of this as just one of several possible answers.

Issue and workaround:

I could confirm about the same situation of your issue (this was reported on Google's Issue Tracker). In this case, I think that when V8 is enabled, the form object might not be able to be parsed when the object is sent to Google Apps Script side with google.script.run. Although I think that this might be modified in the future update, as the current workaround, I would like to propose to send the uploaded file to GAS side as the byte array.

When your script is modified, it becomes as follows.

Modified script:

HTML&Javascript side: ContractUpload.html

Please modify uploadthis as follows.

function uploadthis(fileForm){
const file = fileForm.myFile.files[0];
const fr = new FileReader();
fr.onload = function(e) {
const obj = {
// filename: file.name, // In your script, the filename is given at GAS side. So I removed this.
mimeType: file.type,
bytes: [...new Int8Array(e.target.result)]
};
google.script.run.withSuccessHandler((e) => console.log(e)).uploadFiles(obj);
};
fr.readAsArrayBuffer(file);
}

Google Apps Script side: Code.gs

Please modify uploadFiles as follows.

function uploadFiles(data){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sStamdata = ss.getSheetByName('Stamdata_New');
var contractFolderId = sStamdata.getRange('D60').getValue();
var idag = Utilities.formatDate(new Date(), "GMT+1", "yyyyMMdd");
var title = sStamdata.getRange('D52').getValue();

var file = Utilities.newBlob(data.bytes, data.mimeType, idag+" - KONTRAKT - "+title); // Modified
var folder = DriveApp.getFolderById(contractFolderId);
var createFile = folder.createFile(file);
return createFile.getId(); // Added
}

Note:

  • At above modification, when the file is uploaded, the file is converted to the byte array and send it to GAS side. Then, the file is created from the byte array. And the file ID is returned. You can see it at the console.

References:

  • FileReader()
  • newBlob(data, contentType, name)

If I misunderstood your question and this was not the direction you want, I apologize.

How to create a form with two input fields where both require file uploads, using html and google apps script?

In your situation, how about the following modification?

Modified script:

HTML&Javascript side:

In this modification, please modify your HTML function as follows.

From:

<button type="submit" onclick = submitForm(this.form)>Submit</button>

To:

<button type="submit">Submit</button>

Google Apps Script side:

In this modification, please modify your uploadFiles function as follows.

From:

var fileUrl = "";
var fileName = "";
var fileUrl2 = "";
var fileName2 = "";

//Upload file if exists and update the file url
if (formObject.myFile1.length > 0) {
var blob = formObject.myFile1;
var file = folder.createFile(blob);
file.setDescription("Uploaded by " + formObject.first_name);
fileUrl = file.getUrl();
fileName = file.getName();
} else{
fileUrl = "Record saved without a file";
}

//Upload file if exists and update the file url
if (formObject.myFile2.length > 0) {
var blob2 = formObject.myFile2;
var file2 = folder.createFile(blob2);
file2.setDescription("Uploaded by " + formObject.first_name);
fileUrl2 = file2.getUrl();
fileName2 = file2.getName();
} else{
fileUrl2 = "Record saved without a file";
}

//Saving records to Google Sheet
sheet.appendRow([
formObject.myName,
formObject.myEmail,
formObject.myNum,
fileName,
fileUrl,
fileName2,
fileUrl2,
Utilities.formatDate(new Date(), "GMT+8:00", "yyyy-MM-dd'T'HH:mm:ss'Z'")]);

To:

var files = ["myFile1", "myFile2"].flatMap(e => {
var file = folder.createFile(formObject[e]);
var fileName = file.getName();
var fileUrl = "Record saved without a file";
if (file.getSize() > 0 && fileName != "Untitled") {
file.setDescription("Uploaded by " + formObject.first_name);
fileUrl = file.getUrl();
} else {
fileName = "";
file.setTrashed(true);
}
return [fileName, fileUrl];
});
sheet.appendRow([
formObject.myName,
formObject.myEmail,
formObject.myNum,
...files,
Utilities.formatDate(new Date(), "GMT+8:00", "yyyy-MM-dd'T'HH:mm:ss'Z'")
]);

Note:

  • In December 2021, at V8 runtime, the form object including the file object got to be able to be correctly parsed from Javascript to Google Apps Script. Ref The bug has been resolved. It seems that about the file object, in the current stage, the object can be created as a file using createFile method. But, unfortunately, it seems that the file content cannot be directly retrieved from the form object. So, as a workaround, after the file was created as a file from the parsed form object, I used the method for checking the file size and the filename. If an error like Forms with file inputs must be the only parameter. doesn't occur, when 2nd argument can be used like google.script.run.uploadFiles(formObject, arg2), the process can be simpler. But, in this situation, I used this workaround.

  • When you modified the Google Apps Script, please modify the deployment as a new version. By this, the modified script is reflected in Web Apps. Please be careful this.

  • You can see the detail of this in the report of "Redeploying Web Apps without Changing URL of Web Apps for new IDE".

HTML Service - Form example doesn't work

Here is a workaround I have for file upload from a form in IFRAME mode. This supports multiple files:

code.gs

function doGet() {
return HtmlService.createHtmlOutputFromFile('index').setSandboxMode(HtmlService.SandboxMode.IFRAME);

}

function saveFile(data,name) {

var contentType = data.substring(5,data.indexOf(';'));
var file = Utilities.newBlob(Utilities.base64Decode(data.substr(data.indexOf('base64,')+7)), contentType, name);
DriveApp.getRootFolder().createFile(file);

}

index.html

<div>
<input type="file" id="myFiles" name="myFiles" multiple/>
<input type="button" value="Submit" onclick="SaveFiles()" />
</div>

<script>

var reader = new FileReader();
var files;
var fileCounter = 0;



reader.onloadend = function () {
google.script.run
.withSuccessHandler(function(){
fileCounter++;
postNextFile();
}).saveFile(reader.result,files[fileCounter].name);

}



function SaveFiles(){
files = document.getElementById("myFiles").files;
postNextFile();
}


function postNextFile(){if(fileCounter < files.length){reader.readAsDataURL(files[fileCounter]);}else{fileCounter=0;alert("upload done")}}

</script>

Google app script: upload a csv file from html service

So to sum it up, I had to force Legacy runtime:
Select Run > Disable new Apps Script runtime powered by V8.
Then edit all the V8 syntax:replace "let" by "var" mainly.
Now it's working, thanks to all.

See
https://developers.google.com/apps-script/guides/v8-runtime/migration
https://developers.google.com/apps-script/guides/v8-runtime#enabling_the_v8_runtime



Related Topics



Leave a reply



Submit