Google form: create two rows with different information - google-apps-script

Im trying to create a google form with 2 fields, one for "item" and the other for "quantity"
Since user might need to send miltiple items I want to create 1 form only and sort the information.
my Google form
So far I have managed to add a script that splits the information submitted in "item" into many rows, however, Im not able to do the same with the field "quantity"
I got this information from this post
This is my script:
function onFormSubmit(e) {
var ss = SpreadsheetApp.openByUrl("URL_here");
var sheet = ss.getSheetByName("FormResponses");
// Form Response retrieved from the event object
const formResponse = e.response;
var itemResponses = formResponse.getItemResponses();
// Add responses comma-separated included
var rowData = itemResponses.map(item => item.getResponse().toString());
rowData.splice(0, 0, formResponse.getTimestamp());
// Split into different rows afterwards
if (rowData[1].includes(',')) {
rowData[1].split(',').forEach(instanceName => {
let tmpRow = rowData.map(data => data);
tmpRow[1] = instanceName;
sheet.appendRow(tmpRow);
// Append to the sheet
});
}
else {
sheet.appendRow(rowData); // Append to the sheet
}
Current results:
Click here to see image
What I want to get:
Click here to see image
Thanks

When I saw your script, only the 2nd element of rowData is split with ,. I thought that this might be the reason for your issue. And, when appendRow is used in a loop, the process cost will become high. So, in your situation, how about the following modification?
From:
rowData.splice(0, 0, formResponse.getTimestamp());
// Split into different rows afterwards
if (rowData[1].includes(',')) {
rowData[1].split(',').forEach(instanceName => {
let tmpRow = rowData.map(data => data);
tmpRow[1] = instanceName;
sheet.appendRow(tmpRow);
// Append to the sheet
});
}
else {
sheet.appendRow(rowData); // Append to the sheet
}
To:
var date = formResponse.getTimestamp();
var values = rowData.map(v => v.includes(',') ? v.split(",") : [v]);
var res = values[0].map((_, c) => [date, ...values.map(r => r[c] || "")]);
sheet.getRange(sheet.getLastRow() + 1, 1, res.length, res[0].length).setValues(res);
Reference:
map()

Related

How to insert a cell value from another cell in an appendRow app script?

forgive me, I am new to app script. Thank you in advance.
I have a submit button form with a range of data, the destination is in another sheet. However, I need to insert the date, entity name and office in the appendRow.
The form:
DATE :
Entity Name :
Office :
ID
STOCK NO.
UNIT
DESCRIPTION
QUANTITY
1
JTL-0005
PC
BROOM, SOFT (TAMBO)
1
Output (another sheet) :
DATE
ENTITY NAME
OFFICE
ID
STOCK NO.
UNIT
DESCRIPTION
QUANTITY
1
JTL-0005
PC
BROOM, SOFT (TAMBO)
1
Above output only appends the row. How do I submit it with those values also.
Submit button code:
function btn_dataEntryPrevSubmit(){
var ui = SpreadsheetApp.getUi();
var response = ui.alert('Are you sure you want to continue?',
ui.ButtonSet.YES_NO);
var result = ui.alert('Successfully submitted request!',ui.ButtonSet.OK)
if (response == ui.Button.YES) {
const idCell = formDE.getRange("A4")
/*
let deDate = formDE.getRange("F4")
let deEntityName = formDE.getRange("F8")
let deOffice = formDE.getRange("F10")
*/
let dataEntryRange = formDE.getRange("D14:H")
let values = dataEntryRange.getValues()
for(let index = 0; index < values.length; index++){
db_RequisitionOffice.appendRow(values[index])
const nextIDCell = formDE.getRange("A4")
const nextID = nextIDCell.getValue()
values[index].unshift(nextID)
idCell.setValue(nextID+1)
nextIDCell.setValue(nextID+1)
result
}
//====================================
//DELETE VALUES IN DATA ENTRY FORM
//====================================
const dataEntryFieldRangeForDeletion = ["D14:H"]
dataEntryFieldRangeForDeletion.forEach(f => formDE.getRange(f).clearContent())
}
}
Thank you so much for your help.
Alternate Approach
You may use the map and unshift functions to adjust the output of the script to the proper columns.
Script:
You may use the following script as a basis for your own code:
function adjustData() {
var ss = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Form");
var ssOut = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Output");
var ssLastRow = ss.getLastRow();
var ssLastColumn = ss.getLastColumn()
var deDate = ss.getRange("F4").getValue();
var deEntityName = ss.getRange("F8").getValue();
var deOffice = ss.getRange("F10").getValue();
//Extract Data
var ssData = ss.getRange(14, 1, ssLastRow - 13, ssLastColumn).getValues();
ssData.map(x => {
x.unshift(deDate, deEntityName, deOffice); //Add date, entity name, and office to data
ssOut.appendRow(x); //Transfer data
});
//delete data from form
ss.getRange(14, 1, ssLastRow - 13, ssLastColumn).clear();
}
Result
Since you have not shared a sample spreadsheet, I went ahead and created a test case based on your post.
1. Form Sheet (Input)
2. Output Sheet (Output)
3. Cleared Form Sheet
Reference:
unshift()
map()
appendRow()

Google Apps script large data set transfer [duplicate]

I have a dataset which contains images in col C loaded via formula =IMAGE("") and the need is to refresh the data and have these formulas load the images at destination.
I tried the Spreadsheet API, but handling the data the way it's needed it still far to me - knowledge wise.
I try with the script below, but the column C shows as blank at destination:
function getOrdersData() {
const srcFile = SpreadsheetApp.openById('XXXXXXXXXXX');
const srcSht = srcFile.getSheetByName('Orders');
let srcData = srcSht.getRange(1, 1, srcSht.getLastRow(),
srcSht.getLastColumn()).getValues();
const orderHeaders = srcData[4]; //Colunm headers are actually in row 05
const imgCol = orderHeaders.indexOf('Image');//Whish is where the formulas loading the imgs are
const imgFormulas = srcSht.getRange(1, imgCol + 1, srcSht.getLastRow(), 1).getFormulas();
srcData.forEach(function (row) {
row.splice(imgCol, 1, imgFormulas);
});
const dstFile = SpreadsheetApp.openById('XXXXXXXXXXXXXXX');
const dstSht = dstFile.getSheetByName('Orders v2');
const dstShtLr = dstSht.getLastRow();
if (dstShtLr > 0) {
dstSht.getRange(1, 1, dstShtLr, dstSht.getLastColumn()).clearContent();
}
dstSht.getRange(1, 1, srcData.length, srcData[0].length).setValues(srcData);
}
What can I try next?
In your script, imgFormulas is a 2-dimensional array. In this case, by srcData.forEach(,,,), srcData is not 2 dimensional array. I thought that this might be the reason for your issue. When your script is modified, how about the following modification?
From:
srcData.forEach(function (row) {
row.splice(imgCol, 1, imgFormulas);
});
To:
srcData.forEach(function (row, i) {
if (i > 4) row.splice(imgCol, 1, imgFormulas[i][0]);
});
if (i > 4) was used for considering Colunm headers are actually in row 05.
Note:
In your situation, when Sheets API is used, the sample script is as follows. In this case, please enable Sheets API at Advanced Google services. When the number of cells are large, this might be useful.
function sample() {
const srcSSId = '###'; // Please set source Spreadsheet ID.
const dstSSId = '###'; // Please set destination Spreadsheet ID.
const srcSheetName = 'Orders';
const dstSheetName = 'Orders v2';
const srcValues = Sheets.Spreadsheets.Values.get(srcSSId, srcSheetName).values;
const srcFormulas = Sheets.Spreadsheets.Values.get(srcSSId, srcSheetName, { valueRenderOption: "FORMULA" }).values;
const data = [{ range: dstSheetName, values: srcValues }, { range: dstSheetName, values: srcFormulas }];
Sheets.Spreadsheets.Values.batchUpdate({ valueInputOption: "USER_ENTERED", data }, dstSSId);
}
References:
Method: spreadsheets.values.get
Method: spreadsheets.values.batchUpdate

Append multiple row instead of single row from json

I have script to copy json data to google sheet, bus Is there any way to append multiple rows instead of single row
Can anybody explain how i change this script ? Thanks
function doPost(request = {}) {
const { parameter, postData: { contents, type } = {} } = request; //request data
const { dataReq = {} } = JSON.parse(contents); //content
const { fname = {} } = JSON.parse(contents); //function name
const response = {
status: "function not found: " + fname, // prepare response in function not found
data2: dataReq
}
switch (fname) { //function selection
case 'pasteData':
var output = JSON.stringify(pasteDAta(dataReq)) //call function with data from request
break
default:
var output = JSON.stringify(response)
break
}
return ContentService.createTextOutput(output).setMimeType(ContentService.MimeType.JSON); //response to frontend
}
function pasteDAta(dataReq) {
const id = '1_27rjNQmlXrwVKpLWUbGrJYPJufGRa7Dk-XEKcNAHr0'; //id of Google Sheet
var sheet = SpreadsheetApp.openById(id).getSheetByName('Sheet1'); //sheet
var headings = sheet.getDataRange().getValues()[0]; //Headers
var i = 0 //to test the times that efectively adds rows the forEach function
dataReq.forEach((a) => { //go trought every item on dataReq as 'a'
let holder = []; //to steore temp the elements
for (x in headings) { //to add in order of Headers on sheet
let output = (headings[x] in a) ? a[headings[x]] : ''; //if exist add, if not empty
holder.push(output); //add to holder
}
sheet.appendRow(holder); //put holder(order data) on sheet
i += 1 //to test the times
});
return "Numbers of sheets added: "+i;
}
In your script, how about the following modification?
From:
var i = 0 //to test the times that efectively adds rows the forEach function
dataReq.forEach((a) => { //go trought every item on dataReq as 'a'
let holder = []; //to steore temp the elements
for (x in headings) { //to add in order of Headers on sheet
let output = (headings[x] in a) ? a[headings[x]] : ''; //if exist add, if not empty
holder.push(output); //add to holder
}
sheet.appendRow(holder); //put holder(order data) on sheet
i += 1 //to test the times
});
return "Numbers of sheets added: "+i;
To:
var values = dataReq.map((a) => {
let holder = [];
for (x in headings) {
let output = (headings[x] in a) ? a[headings[x]] : '';
holder.push(output);
}
return holder;
});
var len = values.length;
sheet.getRange(sheet.getLastRow() + 1, 1, len, values[0].length).setValues(values);
return "Numbers of sheets added: " + len;
In this modification, map is used instead of forEach. And, a 2-dimensional array is returned. This array is appended to the sheet using setValues.
Note:
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".
If this modification was not the direct solution to your issue, when you provide the sample value of dataReq, I think that it will help to think of the modification points.
References:
map()
setValues(values)

Importing Importing API data via importJSON

This is my code:
function pricesV2(){
var url='https://prices.runescape.wiki/api/v1/osrs/mapping'
var data = JSON.parse(UrlFetchApp.fetch(url).getContentText())
let myItems = new Map()
let myItem = new Map1()
json=eval('data')
json.forEach(function(elem){myItems.set(elem.id.toString(),elem.name)})
json.forEach(function(elem){myItem.set(elem.id.toString(),elem.examine)})
var url='https://prices.runescape.wiki/api/v1/osrs/latest'
var data = JSON.parse(UrlFetchApp.fetch(url).getContentText())
var result = []
result.push(['#','name','examine','high','low','lowTime', 'highTime'])
for (let p in eval('data.data')) {
try{result.push([p,myItems.get(p),myItem.get(p),data.data.item(p).high,data.data.item(p).low,convertTimestamp(data.data.item(p).lowTime),convertTimestamp(data.data.item(p).highTime)])}catch(e){}
}
return result
}
This is maybe important to know the variables of the API:
function prices(url){
//var url='https://prices.runescape.wiki/api/v1/osrs/latest'
var data = JSON.parse(UrlFetchApp.fetch(url).getContentText())
var result = []
result.push(['#','high','low','highTime','lowTime'])
for (let p in eval('data.data')) {
try{result.push([p,data.data.item(p).high,data.data.item(p).low,data.data.item(p).lowTime, ,data.data.item(p).highTime])}catch(e){}
}
return result
}
function naming(url){
//var url='https://prices.runescape.wiki/api/v1/osrs/mapping'
var data = JSON.parse(UrlFetchApp.fetch(url).getContentText())
var result = []
result.push(["id","name","examine","members","lowalch","limit","value","highalch"])
json=eval('data')
json.forEach(function(elem){
result.push([elem.id.toString(),elem.name,elem.examine,elem.members,elem.lowalch,elem.limit,elem.value,elem.highalch])
})
return result
}
These are 2 API combined (Importing API data via importJSON, solution did work out for 1 element, (element.name)). But when I want to add more from mapping it is giving an error. Could someone help me out? I want to combine all results in one table.
I believe your goal is as follows.
You want to integrate 2 returned data (JSON data) with the value of id.
From your reply of The colums doesn't need in this specific order., you are not required to check the order of columns.
You want to run the script as a custom function.
From your showing script, I thought that you might have wanted to use this script as a custom function.
In this case, how about the following sample script?
Sample script:
Please copy and paste the following script to the script editor of Spreadsheet. And, please put a custom function =SAMPLE() to a cell. By this, the script is run.
function SAMPLE() {
const url1 = "https://prices.runescape.wiki/api/v1/osrs/mapping";
const url2 = "https://prices.runescape.wiki/api/v1/osrs/latest";
const [res1, res2] = [url1, url2].map(url => JSON.parse(UrlFetchApp.fetch(url).getContentText()));
const head = [...Object.keys(res1[0]), ...Object.keys(res2.data[Object.keys(res2.data)[0]])];
const obj1 = res1.reduce((o, e) => (o[e.id] = e, o), {});
const obj2 = Object.entries(res2.data).reduce((o, [k, v]) => (o[k] = v, o), {});
const keys = Object.keys(obj1).map(e => Number(e)).sort((a, b) => a - b);
const values = [head, ...keys.map(k => {
const o = Object.assign(obj1[k], obj2[k]);
return head.map(h => o[h] || "");
})];
return values;
}
Testing:
When this script is run, the following result is obtained.
Note:
If you want to set the specific order of the columns, please modify head in the above script.
When the custom function of =SAMPLE() is put to a cell, if an error occurs, please reopen Spreadsheet and test it again.
If you want to directly put the values to the Spreadsheet instead of the custom function, please modify the script.
References:
Custom Functions in Google Sheets
map()
reduce()
Added:
From the following 3 new questions,
Now how can I change like the top row to- > id, name, examine, members, lowalch, highalch, limit, high, low, lowtime, hightime? How can this be done in the function head, can't edit them individualy?
And also how can I format/convert highTime and lowTime to time (hh:mm:ss)?
From The colums doesn't need in this specific order., I didn't check the order of the column. In that case, as I have already mentioned in my answer, please modify head as follows. About your 2nd new question, in this case, please parse the unix time as follows.
So, when these new 2 questions are reflected in my sample script, it becomes as follows.
Sample script:
function SAMPLE() {
const url1 = "https://prices.runescape.wiki/api/v1/osrs/mapping";
const url2 = "https://prices.runescape.wiki/api/v1/osrs/latest";
const [res1, res2] = [url1, url2].map(url => JSON.parse(UrlFetchApp.fetch(url).getContentText()));
const head = ['id', 'name', 'examine', 'members', 'lowalch', 'highalch', 'limit', 'high', 'low', 'lowTime', 'highTime'];
const obj1 = res1.reduce((o, e) => (o[e.id] = e, o), {});
const obj2 = Object.entries(res2.data).reduce((o, [k, v]) => (o[k] = v, o), {});
const keys = Object.keys(obj1).map(e => Number(e)).sort((a, b) => a - b);
const timeZone = Session.getScriptTimeZone();
const values = [head, ...keys.map(k => {
const o = Object.assign(obj1[k], obj2[k]);
return head.map(h => o[h] ? (['lowTime', 'highTime'].includes(h) ? Utilities.formatDate(new Date(o[h] * 1000), timeZone, "HH:mm:ss") : o[h]) : "");
})];
return values;
}
Note:
About your following 3rd question,
How can this database also be added <prices.runescape.wiki/api/v1/osrs/volumes>?
I think that this is a new question. In this case, please post it as a new question.

How to check if column header exists and if not add header column - Google Sheet Apps Script

I use the following code to insert data from a form into a google sheet.
It works properly and adds data correctly.
The problem is when I want to add data to a column that is not on my worksheet.
I know that I have to check if such a column exists in the worksheet. If it does not exist, add it. And only then add a row with new data.
Unfortunately I'm a freshman in Apps Script and I don't know how to do it
// In case you want to change the Sheet name
var sheetName = 'xyz'
var scriptProp = PropertiesService.getScriptProperties()
// Lowercasing all input keys in the POST data by default (to avoid Message vs message confusion)
var shouldLowerCaseHeaders = true
function intialSetup () {
var activeSpreadsheet = SpreadsheetApp.getActiveSpreadsheet()
scriptProp.setProperty('key', activeSpreadsheet.getId())
}
function filterRow (parameters, mandatoryFields) {
return mandatoryFields.every(field => parameters[field.toString().toLowerCase()] && parameters[field.toString().toLowerCase()].length > 0)
}
function doPost (e) {
var lock = LockService.getScriptLock()
lock.tryLock(10000)
// Uncomment and add fields which must be mandatory when submitting a form
//const mandatoryFields = ['questions']
const mandatoryFields = []
try {
// Get the current open Google Sheet
var doc = SpreadsheetApp.openById(scriptProp.getProperty('key'))
var sheet = doc.getSheetByName(sheetName)
// IMPORTANT: Create headers in your google sheet first
// If you dont create headers this won't match the data
var headers = sheet.getRange(1, 1, 1, sheet.getLastColumn()).getValues()[0]
var nextRow = sheet.getLastRow() + 1
var parameters = e.parameter;
// Lower casing header keys - True by default
if (shouldLowerCaseHeaders){
Object.entries(e.parameter).map(([key, value]) => parameters[key.toString().toLocaleLowerCase()] = value)
}
const shouldInsertToSheet = filterRow(parameters, mandatoryFields)
if (shouldInsertToSheet){
var newRow = headers.map(function(header) {
return header.toString().toLowerCase() === 'timestamp' ? new Date() : parameters[header.toString().toLowerCase()]
})
sheet.getRange(nextRow, 1, 1, newRow.length).setValues([newRow])
}
return HtmlService.createHtmlOutput("post request received");
}
catch (e) {
return HtmlService.createHtmlOutput("post request received");
}
finally {
lock.releaseLock()
}
}
How to check if column header exists and if not add header column?
If you want to add a header that is not included in the initial values, just compare those you have with a new list, if any of the ones that belong to the new list are not included, add them at the end of the headers.
Assuming that the headers of your table are these:
Header1
Header2
Header3
Header4
Code.gs
const sS = SpreadsheetApp.getActiveSheet()
function appendHeaderIfNotExist(headersResponse) {
const actualHeaders = sS.getRange(1, 1, 1, sS.getLastColumn()).getValues().flat()
headersResponse.forEach(h => {
if (!actualHeaders.includes(h)) sS.getRange(1, sS.getLastColumn() + 1).setValue(h)
})
}
function testImplementation() {
appendHeader(['Header1', 'Header5'])
}
After running the function appendHeaderIfNotExist:
Header1
Header2
Header3
Header4
Header 5
You will have to adapt it to work inside your script, probably by parsing the content of the response and calling the function with the array of keys of the response.