6

I'm trying to upload a file to a specific google drive folder along with form data into a spreadsheet. The spreadsheet portion of this code works, but the file upload function does not. Any help on fixing this would be appreciated.

Code.gs

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

function doPost(e) {
  var template = HtmlService.createTemplateFromFile('Thanks.html');
  var LoanType = template.name = e.parameter.name;
  var borrower = template.department = e.parameter.department;
  var amount = template.message = e.parameter.message;
  var emailed = template.email = e.parameter.email;
  var comp = 'N/A'

  var sheet = SpreadsheetApp.openById(submissionSSKey).getSheets()[0];
  var lastRow = sheet.getLastRow();
  var targetRange = sheet.getRange(lastRow+1, 1, 1, 5).setValues([[comp,LoanType,borrower,amount,emailed]]);

  var fileBlob = e.paramater.thefile
  var doc = DriveApp.getFolderById('folder ID');
            doc.createFile(fileBlob)//.rename('New Name');


  return template.evaluate();


}

Form.html

<html>
    <head>
        <title></title>
    </head>
    <body>
    <form action="<?= action ?>" enctype="multipart/form-data" method="post">
    <table border="0" cellpadding="1" cellspacing="0" style="width: 500px;">
    <tbody>
    <tr>
    <td>
    Name:</td>
    <td>
    <input name="name" type="text" /></td>
    </tr>
    <tr>
    <td>
    Department:</td>
    <td>
    <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></td>
    </tr>
    <tr>
    <td>
    Email:</td>
    <td>
    <input name="email" type="text" /></td>
    </tr>
    <tr>
    <td>
    Message:</td>
    <td>
    <textarea name="message" style="margin: 2px; height: 148px; width: 354px;"></textarea></td>
    </tr>
    <tr>
    <td>
    <p>
    School Schedule (Image Files Only):</p>
    </td>
    <td>
    <p>
    <input type="file" id="thefile" name="thefile">
    </p></td>
    </tr>
    <tr>
    <td>
    <input type="submit" value="Submit" /></td>
    <td>
    You will receive an email confirmation upon submission</td>
    </tr>
    </tbody>
    </table>
  </form></body>
</html>

Thanks.html

<html>
  <body>
    <h1>Thanks</h1>
    <p>Thank you for your submission.</p>
    Name: <?= name ?><br/>
    Department: <?= department ?><br/>
    Message: <?= message ?><br/>
    Email: <?= email ?><br/>
  </body>
</html>
Mogsdad
  • 44,709
  • 21
  • 151
  • 275
ryano
  • 231
  • 1
  • 5
  • 18

1 Answers1

10

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
                 ^^^^^^^^^
Community
  • 1
  • 1
Mogsdad
  • 44,709
  • 21
  • 151
  • 275
  • You're absolutely right about the file name from "index" to "form," that was a mistake I made while asking this question. Also, while I feel really stupid for spelling "parameter" wrong, my code is still not working. Here's the execution transcript now: [13-12-25 08:28:39:067 MST] Execution failed: Cannot find method createFile((class)). (line 23, file "Code") [1.735 seconds total runtime] – ryano Dec 25 '13 at 15:42
  • By the way, the code came from of a question a user asked on this site. http://stackoverflow.com/questions/15261247/google-apps-script-create-form-with-file-upload – ryano Dec 25 '13 at 15:45
  • Ah, that clears things up a bit - that question was using UiService, while you're using HtmlService for your form. However, you've kept the same server-side post handling. According to [this answer](http://stackoverflow.com/a/15457041/1677912) from a member of the GAS team, "HtmlService doesn't even use doPost - it uses the google.script.run syntax exclusively." [This follow up answer](http://stackoverflow.com/a/15790713/1677912) has an example using HtmlService and server-side handlers to accomplish the equivalent of the UiService POST for a file upload. – Mogsdad Dec 25 '13 at 17:22
  • I can get one function working (either the file upload or the spreadsheet data inserted) but I can't get both to run together. For now I'm just sticking with App Script Ui and no HTML. – ryano Dec 25 '13 at 18:57
  • 1
    I've updated the answer with a working example using htmlservice. – Mogsdad Dec 25 '13 at 20:20
  • Thanks for that. It works great. Is there an easy way to add another clickhandler type event that shows a loading bar during the upload. I've achieved this on my UiApps fairly easily, but transferring them over to this new setup is still a bit of a challenge for me. – ryano Dec 26 '13 at 18:40
  • Probably can't easily create a progress bar, since `google.script.run` provides no progress feedback other than final success. Outside of this environment, you could attach an HTML5 progress tag and catch events from an `XMLHttpRequest`, but those requests to the Google App Server are forbidden. – Mogsdad Dec 26 '13 at 23:06
  • Sorry, I guess I should've specified a little bit more what I meant by loading bar. In my UiApps I have this code: `var statusLabel = app.createHorizontalPanel().add(app.createImage('https://dl.dropboxusercontent.com/u/211279/loading3T.gif')).add(app.createHTML("Uploading. Please wait...")).setId('status').setVisible(false);` It just "enables" on submut click by `.setVisible(true)`, and displays the .gif from that link before the Thank you message comes up. – ryano Dec 27 '13 at 16:54
  • No problem, then! In the same way that the form is being hidden after submission, we can use `onClick=` with multiple javascript functions to display a progress `
    ` that starts hidden. I've updated Form.html in the answer to do this.
    – Mogsdad Dec 27 '13 at 17:26
  • 1
    Thank you so much for that! The `toggle_visibility` script is a beautiful little tool. I went ahead and added `style="display: block;` to the `id="formDiv"` section so that it would disappear on submit click. It's exactly what I needed. I also added `LoadingUploading. Please wait...` to the `id="inProgress"` section – ryano Dec 27 '13 at 23:37