Last active
April 27, 2021 03:41
-
-
Save jameswilson/0dea86b63741ee4a723cc568c09796ea to your computer and use it in GitHub Desktop.
Google Sheets XHR insert via GET request, respond with JSON status.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/*! | |
* @file | |
* Google Sheets XHR insert script. | |
* | |
* From: https://gist.github.com/jameswilson/0dea86b63741ee4a723cc568c09796ea | |
* | |
* Setup instructions: | |
* | |
* 1. Create a new Google Sheet and add column names in Row 1. | |
* 2. Click "Tools" > "Script editor" from the toolbar. | |
* 3. Copy/Paste this Code.js script into the Google Scripts UI. | |
* 4. Click "Publish" > "Deploy as web app..." from the toolbar. | |
* 5. Ensure Project version is set to "1". (Choose "New" if | |
* deploying code changes.) | |
* 6. Ensure "Execute the app as" is set to "Me". | |
* 7. Ensure "Who has access" is set to "Anyone, Anonymous". | |
* 8. Save the generated "Current web app URL" for use in your | |
* HTML form in the `google_script` variable. | |
* 9. Create and customize HTML form to match the spreadsheet | |
* columns defined in step 1. (See example form.html in gist). | |
* | |
* Inspired by https://medium.com/@dmccoy/how-to-submit-an-html-form-to-google-sheets-without-google-forms-b833952cc175 | |
*/ | |
/** | |
* Sheet name (aka tab name) in Google sheets. | |
*/ | |
var SHEET_NAME = "Form Responses"; | |
/** | |
* Globally persistent properties across requests. | |
*/ | |
var SCRIPT_PROP = PropertiesService.getScriptProperties(); | |
/** | |
* One time setup command to initialize Sheets <=> Scripts integration. | |
* | |
* After pasting this script into Google Scripts UI, and publishing. Run the | |
* `setup` command once from within the Google Sheet UI where the script is | |
* attached. | |
*/ | |
function setup() { | |
var doc = SpreadsheetApp.getActiveSpreadsheet(); | |
SCRIPT_PROP.setProperty("key", doc.getId()); | |
} | |
/** | |
* Allows the Google Sheet to handle GET requests. | |
*/ | |
function doGet(e) { | |
return handleResponse(e); | |
} | |
/** | |
* Insert data into the Google Sheet and prepare the JSON response. | |
* | |
* Variables are parsed out from the GET request's query string, sent in the | |
* format: key1=value1&key2=value2, where key1 and key2 match exactly to | |
* specific column names from the spreadsheet. Key names are case sensitive and | |
* space characters should be avoided; CamelCase or snake_case are recommended. | |
*/ | |
function handleResponse(e) { | |
// Google's LockService[1] prevents concurrent access overwritting data. | |
// [1] http://googleappsdeveloper.blogspot.co.uk/2011/10/concurrency-and-google-apps-script.html | |
// The public lock blocks all requests globally while data is being inserted. | |
var lock = LockService.getPublicLock(); | |
// Wait to obtain a lock for up to 30 seconds before conceding defeat. | |
lock.waitLock(30000); | |
try { | |
// Prep the spreadsheet where this script is attached for data insert. | |
var doc = SpreadsheetApp.openById(SCRIPT_PROP.getProperty("key")); | |
var sheet = doc.getSheetByName(SHEET_NAME); | |
// Assume spreadsheet header row that contains the column names is in row 1. | |
// The header row may be overridden by passing header_row=N (where N is the | |
// integer row number) via the GET request data. | |
var headRow = e.parameter.header_row || 1; | |
var headers = sheet.getRange(1, 1, 1, sheet.getLastColumn()).getValues()[0]; | |
var nextRow = sheet.getLastRow() + 1; | |
var row = []; | |
// Loop through the header row's column names. | |
for (i in headers) { | |
// If the spreadsheet contains a 'Timestamp' column, then insert the | |
// current datetime. | |
if (headers[i] == "Timestamp") { | |
row.push(new Date()); | |
// Otherwise use column name to get data. | |
} else { | |
row.push(e.parameter[headers[i]]); | |
} | |
} | |
// It is more efficient to set values as [][] array than individually. | |
sheet.getRange(nextRow, 1, 1, row.length).setValues([row]); | |
// On success, return a simple JSON response with the row number that was | |
// inserted. | |
return ContentService | |
.createTextOutput(JSON.stringify({"result":"success", "row": nextRow})) | |
.setMimeType(ContentService.MimeType.JSON); | |
} catch(e) { | |
// On error, return the payload sent via GET request for debugging. | |
return ContentService | |
.createTextOutput(JSON.stringify({"result":"error", "error": e})) | |
.setMimeType(ContentService.MimeType.JSON); | |
} finally { | |
lock.releaseLock(); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!-- | |
This example leverages modern ES6 async features with fetch() | |
API which may require polyfills for older browsers. | |
--> | |
<style> | |
body { | |
display: flex; | |
align-items: center; | |
align-content: center; | |
justify-content: center; | |
font-family: sans-serif; | |
} | |
label { | |
display: block; | |
} | |
label span { | |
color: red; | |
} | |
form { | |
padding: 2rem; | |
border: 1px solid; | |
border-radius: 3px; | |
} | |
input, textarea, button { | |
padding: .4rem .2rem; | |
border: 1px solid #ccc; | |
border-radius: 3px; | |
font-size: 1rem; | |
min-width: 17rem; | |
} | |
button { | |
background: dodgerblue; | |
border-color: dodgerblue; | |
color: white; | |
} | |
button, | |
[role="listitem"] + [role="listitem"] { | |
margin-top: 1rem; | |
} | |
</style> | |
<form id="formElem"> | |
<div role="list"> | |
<div role="listitem"> | |
<label for="Email" role="heading" aria-level="3">Email address <span aria-label="Required question">*</span></label> | |
<input type="email" autocomplete="email" aria-label="Your email" name="Email" id="Email" value="" required> | |
</div> | |
<div role="listitem"> | |
<label for="Name" role="heading" aria-level="3">Your name <span aria-label="Required question">*</span></label> | |
<input type="text" autocomplete="name" aria-label="Your name" name="Name" id="Name" value="" required> | |
</div> | |
<div role="listitem"> | |
<label for="Phone" role="heading" aria-level="3">Phone number</label> | |
<input type="text" autocomplete="tel-national" aria-label="Phone number" name="Phone" id="Phone" value=""> | |
</div> | |
<div role="listitem"> | |
<label for="Comment" role="heading" aria-level="3">Tell us more <span aria-label="Required question">*</span></label> | |
<textarea rows="3" aria-label="Tell us more about this photo" name="Comment" id="Comment" required></textarea> | |
</div> | |
<div role="listitem"> | |
<label for="Url" role="heading" aria-level="3">Url <span aria-label="Required question">*</span></label> | |
<input type="url" autocomplete="off" aria-label="Url" name="Url" id="Url" value="" required> | |
</div> | |
</div> | |
<button type="submit">Submit</button> | |
</form> | |
<script> | |
const google_script = 'https://script.google.com/macros/s/XXXXXXXXXXXXXXXXXXXXX/exec'; | |
formElem.addEventListener('submit', async (event) => { | |
event.preventDefault(); | |
const formData = new FormData(event.target); | |
const params = new URLSearchParams(formData); | |
const url = google_script + '?' + params.toString(); | |
let response = await fetch(url); | |
let result = await response.json(); | |
console.log(result); | |
}) | |
</script> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment