I'm trying to send a multi-step web form to a Google sheet, using Google apps script and Ajax.
And because it's a public form and in multi-steps, I want to save the second ajax request to the same row as the first ajax request. And to avoid duplicated submissions, I save a unique id in the first time and check against it in the next submissions.
Assuming this is the sheet structure:
Timestamp step1-col step2-col uniqueId
---------------------------------------------------------------------
1547266627717 step1-data step2-data 1234
My code in GAS as follows:
var uniqueIdColIndex = 4, step2ColIndex = 3;
function doGet(e) {
return handleResponse(e);
}
function handleResponse(e) {
var lock = LockService.getPublicLock();
lock.waitLock(30000);
try {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var headers = sheet.getRange(1, 1, 1, sheet.getLastColumn()).getValues()[0];
var nextRow = sheet.getLastRow() + 1;
var row = [];
//make a search for the uniqueId;
var uniqueIdColValues = sheet.getRange(2, uniqueIdColIndex, sheet.getLastRow()).getValues()[0];
var uniqueIdRowIndex = uniqueIdColValues.indexOf(e.parameters.uniqueId);
//if the uniqueId were saved before:
if (uniqueIdRowIndex > -1) { //<--- this always fail! why?
//make another search for the step2-value;
var step2Data = sheet.getRange(uniqueIdColIndex + 1, step2ColIndex).getValues()[0];
//if the step2-data wasn't saved before, save it;
if (!step2Data) {
sheet.getRange(uniqueIdColIndex + 1, step2ColIndex).setValues([e.parameters.step2data]);
// return a json success results
return ContentService
.createTextOutput(JSON.stringify({"result": "success", "row": nextRow}))
.setMimeType(ContentService.MimeType.JSON);
}
//else: continue writing the form to the sheet;
} else {
//loop through the header columns
for (var i = 0; i < headers.length; i++) {
if (headers[i] === "Timestamp") { //save a 'Timestamp'
row.push(new Date().getTime());
} else { //else use the header names to get data
if (!e.parameters[headers[i]]) {
row.push('');
} else {
row.push(e.parameters[headers[i]]);
}
}
}
//save the data for the first step;
sheet.getRange(nextRow, 1, 1, row.length).setValues([row]);
//return a json success
return ContentService
.createTextOutput(JSON.stringify({"result": "success", "row": nextRow}))
.setMimeType(ContentService.MimeType.JSON);
}
} catch (e) {
// if error return this
return ContentService
.createTextOutput(JSON.stringify({"result": "error", "error": e}))
.setMimeType(ContentService.MimeType.JSON);
} finally { //release lock
lock.releaseLock();
}
}
The problem is:
I always end up getting duplicated submissions in 2 rows like so:
Timestamp step1-col step2-col uniqueId
---------------------------------------------------------------------
1547266627717 step1-data 1234
1547266647568 step2-data 1234
Which happens because my uniqueId check always fails, I guess.
What am I doing wrong?
And how can I merge both steps/ajax requests to the same row?
Related
(React web app development)
In order to check if the current stock status of products, I use ID of products to loop through json data.
I am trying to retrieve value of "DATAPAYLOAD" by key (id) from json (below). idsent is string passed from another component. But "if (Data.response[i].id === idsent)" this condition always appears to be false because I got "failed" in console.
That would be really helpful if someone could take a look at the following code and give me some sujections, thanks in advance!
onButtonClicked = () => {
const idsent="D56F36C6038DFC8244F"
for (var i = 0; i < Data.response.length; i++) {
if (Data.response[i].id === idsent) {
name = Data.response[i].DATAPAYLOAD;
const word = '<INSTOCKVALUE>INSTOCK</INSTOCKVALUE>';
if (name.includes(word)) {
return true;
}
else {
return false;
}
}
console.log("failed");
}
The following is part of the json data that is requested through fetch get-method.
Data= {
"code": 200,
"response": [
{
"id": "CED62C6F96BD0E21655142F",
"DATAPAYLOAD": "<AVAILABILITY>\n <CODE>200</CODE>\n
<INSTOCKVALUE>OUTOFSTOCK</INSTOCKVALUE>\n</AVAILABILITY>"
},
{
"id": "D56F36C6038DFC8244F",
"DATAPAYLOAD": "<AVAILABILITY>\n <CODE>200</CODE>\n
<INSTOCKVALUE>LESSTHAN10</INSTOCKVALUE>\n</AVAILABILITY>"
},
{
"id": "4536C9E608B563A749",
"DATAPAYLOAD": "<AVAILABILITY>\n <CODE>200</CODE>\n
<INSTOCKVALUE>INSTOCK</INSTOCKVALUE>\n</AVAILABILITY>"
},
{
"id": "3A576872130625CABFADEE68",
"DATAPAYLOAD": "<AVAILABILITY>\n <CODE>200</CODE>\n
<INSTOCKVALUE>INSTOCK</INSTOCKVALUE>\n</AVAILABILITY>"
}
]
}
Thank you again.
You probably wanted console.log("failed"); outside of the for loop like the following (so that it only executes once all the data is processed):
onButtonClicked = () => {
const idsent="D56F36C6038DFC8244F"
for (var i = 0; i < Data.response.length; i++) {
if (Data.response[i].id === idsent) {
name = Data.response[i].DATAPAYLOAD;
const word = '<INSTOCKVALUE>INSTOCK</INSTOCKVALUE>';
if (name.includes(word)) {
return true;
}
else {
return false;
}
}
}
console.log("failed");
When the fetch is successful, You need to read and parse the data using json(). Pleas read this
onButtonClicked = async () => {
const idsent="D56F36C6038DFC8244F"
Data = await Data.json(); // json() will create a promise
for (var i = 0; i < Data.response.length; i++) {
if (Data.response[i].id === idsent) {
name = Data.response[i].DATAPAYLOAD;
const word = '<INSTOCKVALUE>INSTOCK</INSTOCKVALUE>';
if (name.includes(word)) {
return true;
}
else {
return false;
}
}
console.log("failed");
}
The reason you get failed, is because the first time through, the ID does not match the one sent, so it console logs the "failed" message. Then the second time through the for loop it matches the data, and then hits the next if, which checks for the value. Since the value you are searching for is included in the data, it returns true and the for loop is exited. The reason you see the fail log is because you are logging when the id doesn't match and there are 3 records in that array where the id don't match, the first one being one of them.
This question already has answers here:
How do I return the response from an asynchronous call?
(41 answers)
Closed 3 years ago.
I'm still very new to JavaScript, but the basic premise of my program is to make an API call, and then turn that data into a table. I have tested the buildHtmlTable function and it works fine with a sample array that I pre-populated with static data (not from API).
In the buildHtmlTable function console.log(myList.length) returns 0. This is most likely where to problem stems from because if length is 0 then for (var i = 0; i < myList.length; i++ does not run at all.
I've also tried adding data to my table using .push and seem to get the same errors.
Here is what my code looks like:
<body onLoad="buildHtmlTable('#excelDataTable')">
<table id="excelDataTable" border="1">
</table>
</body>
<script>
var myList = [];
function getAPIData() {
// Create a request variable and assign a new XMLHttpRequest object to it.
var request = new XMLHttpRequest()
// Open a new connection, using the GET request on the URL endpoint
request.open('GET', '/api/table=1/records/', true)
request.onload = function () {
// Begin accessing JSON data here
var data = JSON.parse(this.response)
n = 0
if (request.status >= 200 && request.status < 400) {
data.forEach(record => {
myList[n] = (record.data);
n++;
//console.log(record.data.name)
})
} else {
console.log('error')
}
}
request.send()
console.log('fin')
}
// Builds the HTML Table out of myList.
function buildHtmlTable(selector) {
getAPIData()
console.log(myList.length)
console.log(1)
var columns = addAllColumnHeaders(myList, selector);
console.log(1.1)
console.log(myList.length)
for (var i = 0; i < myList.length; i++) {
console.log(1.2)
var row$ = $('<tr/>');
console.log(1.3)
for (var colIndex = 0; colIndex < columns.length; colIndex++) {
var cellValue = myList[i][columns[colIndex]];
if (cellValue == null) cellValue = "";
row$.append($('<td/>').html(cellValue));
}
$(selector).append(row$);
}
console.log(2)
}
// Adds a header row to the table and returns the set of columns.
// Need to do union of keys from all records as some records may not contain
// all records.
function addAllColumnHeaders(myList, selector) {
var columnSet = [];
var headerTr$ = $('<tr/>');
for (var i = 0; i < myList.length; i++) {
var rowHash = myList[i];
for (var key in rowHash) {
if ($.inArray(key, columnSet) == -1) {
columnSet.push(key);
headerTr$.append($('<th/>').html(key));
}
}
}
$(selector).append(headerTr$);
return columnSet;
}
</script>
As for the requested data, this is what the returned JSON looks like:
[
{
"id": 1,
"data": {
"name": "John Doe",
"id": "5d7861f38319f297df433ae1"
}
},
{
"id": 2,
"data": {
"name": "John deer",
"id": "5d7861f38319f297df433ae1"
}
},
{
"id": 3,
"data": {
"name": "Jane Doe",
"id": "5d79126f48ca13121d673300"
}
}
]
Any idea on where I am going wrong here? Thanks.
Edit: Here is what my implementation using fetch. However, I'm still getting an array with a length of 0.
async function getAPIData() {
const response = await fetch('/api/table=1/records/')
const myJson = await response.json();
myJson.forEach(record => {
myList.push(record.data);
})
}
XMLHttpRequests are asynchronous, meaning the rest of your code won't wait for them to finish before they run. When you call getAPIData(), it starts making the request, but then it goes to the next line of buildHtmlTable before the request is complete (and thus before the list is populated). What you should be doing is calling the getAPIData function outside the buildHtmlTable function, then calling buildHtmlTable in the onload callback of the XHR request. This will ensure the data is loaded and populated by the time the HTML building function is run.
You could also switch to using fetch instead of XMLHttpRequest; since fetch returns a promise, you can use the ES6 async / await syntax to just await the API response before the code inside the buildHtmlTable function continues. But that's a new way to think about AJAX and asynchronous behavior, so if you're not used to it, I'd say stick to my first suggestion instead.
I'm trying to submit a contact form using google-app-script. When I'm submit that, it displays the following page:
https://script.google.com/macros/s/AKfycbx-sxqjOmCD5pGhLzzcI9Tc906bZN5VnNQQCe34/exec
I want this url to to redirect to my another page, how can I achieve that using scripts.
Below is the script. Just for the info, I'm a beginner.
/******************************************************************************
* This tutorial is based on the work of Martin Hawksey twitter.com/mhawksey *
* But has been simplified and cleaned up to make it more beginner friendly *
* All credit still goes to Martin and any issues/complaints/questions to me. *
******************************************************************************/
// if you want to store your email server-side (hidden), uncomment the next line
var TO_ADDRESS = "abc#gmail.com";
// spit out all the keys/values from the form in HTML for email
// uses an array of keys if provided or the object to determine field order
function formatMailBody(obj, order) {
var result = "";
if (!order) {
order = Object.keys(obj);
}
// loop over all keys in the ordered form data
for (var idx in order) {
var key = order[idx];
result += "<h4 style='text-transform: capitalize; margin-bottom: 0'>" + key + "</h4><div>" + sanitizeInput(obj[key]) + "</div>";
// for every key, concatenate an `<h4 />`/`<div />` pairing of the key name and its value,
// and append it to the `result` string created at the start.
}
return result; // once the looping is done, `result` will be one long string to put in the email body
}
// sanitize content from the user - trust no one
// ref: https://developers.google.com/apps-script/reference/html/html-output#appendUntrusted(String)
function sanitizeInput(rawInput) {
var placeholder = HtmlService.createHtmlOutput(" ");
placeholder.appendUntrusted(rawInput);
return placeholder.getContent();
}
function doPost(e) {
try {
Logger.log(e); // the Google Script version of console.log see: Class Logger
record_data(e);
// shorter name for form data
var mailData = e.parameters;
// names and order of form elements (if set)
var orderParameter = e.parameters.formDataNameOrder;
var dataOrder;
if (orderParameter) {
dataOrder = JSON.parse(orderParameter);
}
// determine recepient of the email
// if you have your email uncommented above, it uses that `TO_ADDRESS`
// otherwise, it defaults to the email provided by the form's data attribute
var sendEmailTo = (typeof TO_ADDRESS !== "undefined") ? TO_ADDRESS : mailData.formGoogleSendEmail;
// send email if to address is set
if (sendEmailTo) {
MailApp.sendEmail({
to: String(sendEmailTo),
subject: "About Me: Contact form submitted",
// replyTo: String(mailData.email), // This is optional and reliant on your form actually collecting a field named `email`
htmlBody: formatMailBody(mailData, dataOrder)
});
}
return ContentService // return json success results
.createTextOutput(
JSON.stringify({"result":"success",
"data": JSON.stringify(e.parameters) }))
.setMimeType(ContentService.MimeType.JSON);
} catch(error) { // if error return this
Logger.log(error);
return ContentService
.createTextOutput(JSON.stringify({"result":"error", "error": error}))
.setMimeType(ContentService.MimeType.JSON);
}
}
/**
* record_data inserts the data received from the html form submission
* e is the data received from the POST
*/
function record_data(e) {
var lock = LockService.getDocumentLock();
lock.waitLock(30000); // hold off up to 30 sec to avoid concurrent writing
try {
Logger.log(JSON.stringify(e)); // log the POST data in case we need to debug it
// select the 'responses' sheet by default
var doc = SpreadsheetApp.getActiveSpreadsheet();
var sheetName = e.parameters.formGoogleSheetName || "responses";
var sheet = doc.getSheetByName(sheetName);
var oldHeader = sheet.getRange(1, 1, 1, sheet.getLastColumn()).getValues()[0];
var newHeader = oldHeader.slice();
var fieldsFromForm = getDataColumns(e.parameters);
var row = [new Date()]; // first element in the row should always be a timestamp
// loop through the header columns
for (var i = 1; i < oldHeader.length; i++) { // start at 1 to avoid Timestamp column
var field = oldHeader[i];
var output = getFieldFromData(field, e.parameters);
row.push(output);
// mark as stored by removing from form fields
var formIndex = fieldsFromForm.indexOf(field);
if (formIndex > -1) {
fieldsFromForm.splice(formIndex, 1);
}
}
// set any new fields in our form
for (var i = 0; i < fieldsFromForm.length; i++) {
var field = fieldsFromForm[i];
var output = getFieldFromData(field, e.parameters);
row.push(output);
newHeader.push(field);
}
// more efficient to set values as [][] array than individually
var nextRow = sheet.getLastRow() + 1; // get next row
sheet.getRange(nextRow, 1, 1, row.length).setValues([row]);
// update header row with any new data
if (newHeader.length > oldHeader.length) {
sheet.getRange(1, 1, 1, newHeader.length).setValues([newHeader]);
}
}
catch(error) {
Logger.log(error);
}
finally {
lock.releaseLock();
return;
}
}
function getDataColumns(data) {
return Object.keys(data).filter(function(column) {
return !(column === 'formDataNameOrder' || column === 'formGoogleSheetName' || column === 'formGoogleSendEmail' || column === 'honeypot');
});
}
function getFieldFromData(field, data) {
var values = data[field] || '';
var output = values.join ? values.join(', ') : values;
return output;
}
I tried to see your script, but it return this error: Script function not found: doGet
However, you could try do this...
window.location.href = "YOUR_URL";
I am using Google Apps Script
I have a Google Sheet where column 1 has 120 domain names (120 rows) and i need to write the status of these domains as "Domain Verified" / "Domain Not Verified" in Column 7
I wrote a script which is using Admin Directory API service and using AdminDirectory.get.domains.verified which results in boolean (True = domain verified, False = domain not verified) to check the status of domains to see if they are verified domains in google.
my below script works absolutely fine, it checks for each domain name row and puts the status as either in column 7, however the problem is that my loop stops as soon as it comes to any domain which is NOT yet registered in Google, in the logs it says "Execution failed: Domain not found. (line 36, file "Code") [1.412 seconds total runtime]"
where i expect it to be running till the last row (120th) regardless of the result.
What I actually want is, regardless of the result, my loop should cover all 120 rows, can you help?
Here is my Script-:
function domainList() {
var ss = SpreadsheetApp.getActiveSpreadsheet()
var sheet = ss.getActiveSheet()
var data = sheet.getRange("B2:B").getValues()
var customer = "my_customer"
for(i=0; i<data.length; i++){
var dcheck = AdminDirectory.Domains.get(customer, data[i]);
var status = dcheck.verified
if(status === true){
status = "Domain Verified"}
else if(status === false){
status = "Domain Not Verified"}
else if(status === ({})){
continue;
}
Logger.log(status)
var range = ss.getSheets()[0].getRange(2+i, 7).clear()
var range = ss.getSheets()[0].getRange(2+i, 7)
range.setValue(status)
}}
You could try putting some of the code into a try/catch and if there is an error, simply continue looping:
try {
var dcheck = AdminDirectory.Domains.get(customer, data[i]);
if (dcheck) {//Check for truthy value
var status = dcheck.verified;
} else {
continue;//If "get" returned a falsy value then continue
}
} catch(e) {
continue;//If error continue looping
}
Here's another way of doing the same thing. I wasn't clear on what this if(status === ({})) was so I thought it might be interesting to make that third alternative a little less narrow because if it's expected to be boolean and it's neither true nor false then it's not clear to me what else is left. It might take a while but I'd try to run through this with the debugger. But I'm not familiar with AdminDirectory so I'm kinda guessing here.
function domainList()
{
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var data = sheet.getRange("B2:B").getValues();
var customer = "my_customer";
var value = '';
for(i=0; i<data.length; i++)
{
value = '';
var dcheck = AdminDirectory.Domains.get(customer, data[i]);
var status = dcheck.verified;
switch(status)
{
case true:
value = 'Domain Verified';
break;
case false:
value = 'Domain Not Verified';
break;
default:
value = 'unknown';
}
Logger.log(status);
var range = ss.getSheets()[0].getRange(2+i, 7)
range.clear();
range.setValue(value);
}
}
I'm trying to write a simple Content Service API with Google App Script. My doGet() just takes a single param 'row' and returns the value in that row from col 2. Here it is:
function doGet(req){
try{
reqRow = req.queryString.split('=')[1];
rowNum = parseInt(reqRow);
}
catch(err){
throw "Error: "+ err + " -- Here's what we got: " + req + "Did you include a row parameter?";
}
resultValue = getFromSheet(rowNum);
if(resultValue != ""){
result = {
"Result":"Success",
"Row": reqRow,
"Value" : resultValue
}
}
else{
result = {
"Result":"Failure",
"Row": reqRow,
"Value" : "No value found at row"
}
}
return ContentService.createTextOutput(JSON.stringify(result)).setMimeType(ContentService.MimeType.JSON);
}
And here's our simple getFromSheet function:
function getFromSheet(row){
if(typeof row != "number"){
row = Number(row);
Logger.log("converting");
}
sheetid = 111111111;
ss = SpreadsheetApp.openById(sheetid);
sheet = ss.getSheets()[0];
return ss.getRange(row, 2).getValue();
}
When I post something to it, via a url similar to
"https://script.google.com/macros/s/111111111/exec?row=3"
I get the following error:
Cannot find method getRange(number,number).
Note: Why didn't I use req.parameters.row in our doGet() method? Because I was receiving the following error:
Cannot find method getRange((class),number).
Isn't getRange(number, number) the main way to call this method?
Here is the reference guide to the sheet class:
https://developers.google.com/apps-script/reference/spreadsheet/sheet
Thank you all!
Changed the variable name to rowRequest instead of row.
function getFromSheet(rowRequest){
if(typeof rowRequest != "number"){
rowRequest = Number(rowRequest);
Logger.log("converting");
}
ss = SpreadsheetApp.openById('12r0gHSgvBg8s_RJIuWJl-3aZCAGOpW0PfGb0SVGNu4Q');
sheet = ss.getSheets()[0];
result = sheet.getRange(rowRequest, 2).getValue();
return result;
}