I am creating a google form to capture data from multiple users. This form has 13 fields. 3 of the form fields are drop down which are populating data from a sheet. This part of the code is completed. But I got struck with the following scenario.
One of the fields captures instance details (production, UAT) and has checkbox option. I thought I would be able to create two rows in the response sheet when instance field has select on two check boxes but learnt that’s not how google form works. So I am looking for a scripting option to do the following.
When the user select PRD and UAT for the instance, two rows to be created in the form response sheet on when the form is created.
The data for the new rows created in #1 will remain the same for the two rows expect for the column instance which will adopt the checkbox value from the form in the respective rows.
If only one option is selected then only one row is added to the response sheet
My experience in google app scripting or Java is very limited. With my limited knowledge I was able to get the responses from the form but not sure how to create an additional row when the condition is met (as mentioned above). Taking one step at a time to understand the form architecture
Code 1:
This is to get the title, index and type of the fields in the form. So I know some information of the form (still learning)
function testgetFormDetails()
{
var form = FormApp.getActiveForm();
var items = form.getItems();
for (var i in items)
{
Logger.log(items[i].getTitle() +', ID - '+
items[i].getId() +', Type - ' +
items[i].getType() +' , Form Index - '+
items[i].getIndex());
}
}
Following is the execution log
**Execution log**
5:49:38 PM Notice Execution started
5:49:39 PM Info Business Group, ID - 286404828, Type - CHECKBOX , Form Index - 0
5:49:39 PM Info Instance, ID - 1043278952, Type - CHECKBOX , Form Index - 1
5:49:40 PM Notice Execution completed
Code 2:
Get responses for the questions (small progress)
function getResponseForInstance()
{
var formResponses = FormApp.getActiveForm().getResponses();
Logger.log(formResponses.length);
for (var i = 0; i < formResponses.length; i++)
{
var formResponse = formResponses[i];
var itemResponses = formResponse.getItemResponses();
for (var j = 0; j < itemResponses.length; j++)
{
var itemResponse = itemResponses[j];
var lookfor = 'UAT,PRD'
if(itemResponse.getResponse() == lookfor )
{
Logger.log('Question:' + itemResponse.getItem().getTitle() + ' Response:' + itemResponse.getResponse() )
}
}
}
}
The execution log shows me the row number, question and the response
**Execution log**
8:22:18 PM Info Question:Instance Response:UAT,PRD
8:22:18 PM Info Question:Instance Response:UAT,PRD
Now I have to marry both to create an additional row in the response spreadsheet and have been racking my brains on this. All I know atm is the **Logger.Log()** line will be replaced by additional code to add 2 rows when the condition is met.
Any help on this will be very much appreciated.
Look forward to your support and guidance.
Adding example screenshots per #Jose Vasquez
Sample Form
Actual Form Response
Expected Response - row two has been split into 2 row with column data in C2 is parsed into PRD and UAT per row and the reminder of the data remains the same for line 2 and line 3
OnFormSubmit Function results
Thanks
Al
You can split into different rows by filtering afterwards
Here's my approach (No triggers, only run and process all the current responses):
function processResponses() {
var ss = SpreadsheetApp.openById("SPREADSHEET_ID");
var sheet = ss.getSheetByName("SHEET_NAME");
const formResponses = FormApp.getActiveForm().getResponses();
for (var i = 0; i < formResponses.length; i++) {
var formResponse = formResponses[i];
var itemResponses = formResponse.getItemResponses();
// Add responses comma-separated included
var rowData = itemResponses.map(item => item.getResponse().toString());
rowData.splice(0, 0, formResponse.getTimestamp()); // Timestamp
// Split into different rows afterwards
if (rowData[2].includes(',')) {
rowData[2].split(',').forEach(instanceName => {
let tmpRow = rowData.map(data => data);
tmpRow[2] = instanceName;
sheet.appendRow(tmpRow); // Append to the sheet
});
} else {
sheet.appendRow(rowData); // Append to the sheet
}
}
}
First of all open the Spreadsheet where you'll store your responses. Having done that, iterate through your responses as you were already doing and then add all of your responses including the timestamp for each form response.
After adding the response data into rowData you can evaluate if the Instance Column (column 2 or Column C) includes a comma. If so, simply split this field by this character and then iterate through this data in order to append a new row into your sheet for each "instance".
On Form Submit trigger (keep in mind that you have to install it)
Retrieve the response from the Event Object.
function onFormSubmit(e) {
var ss = SpreadsheetApp.openById("SPREADSHEET_ID");
var sheet = ss.getSheetByName("SHEET_NAME");
// 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[2].includes(',')) {
rowData[2].split(',').forEach(instanceName => {
let tmpRow = rowData.map(data => data);
tmpRow[2] = instanceName;
sheet.appendRow(tmpRow); // Append to the sheet
});
} else {
sheet.appendRow(rowData); // Append to the sheet
}
}
References
FormResponse.getTimestamp
Sheet.appendRow
Related
The script, that I will post below, allows me to use a check box to move an entire row from one sheet to another in the same workbook. Everything works (I was following a YouTube tutorial), but it moves the entire row of data. Instead, I need it to move only data from specific columns, i.e. move the info from column A to C or move the info from column A and column K. I know this might be a simple question, but I just don't know which line to modify and how to modify it so that the script only pulls the info from specific columns within the row to move to the other sheet.
function sendMail() {
var client = 3;
var date = 0;
var email = 19;
var dnr = 18;
var emailTemp = HtmlService.createTemplateFromFile("email");
var ws = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Promos");
var data = ws.getRange("A3:T" + ws.getLastRow()).getValues();
data = data.filter(function(r){return r[1] == true })
data.forEach(function(row){
emailTemp.client = row[client];
emailTemp.date = row[date];
emailTemp.dnr = row[dnr];
var htmlMessage = emailTemp.evaluate().getContent();
GmailApp.sendEmail(row[email],
"Promo Reminder",
"Your email does not support html",
{name: "JV Ops", htmlBody: htmlMessage}
);
});
}
I tried to modify the line "var row = range.getRow()" by putting column letters in the parenthesis but it didn't do anything, not only did it not move the info from those specific columns but it no longer moved the entire row. It just no longer did anything.
You have a spreadsheet in which a "checked" checkbox identified the row(s) that are to be sent an email. However you can't identify the actual row number of the checked checkbox.
There are two issues in your script:
data = data.filter(function(r){return r[1] == true }) replaces the array that contains ALL the data on the sheet with just those rows that contain checked checkboxes.
you need to compare the new filtered array with the original array in order to get the row numbers. I adapted the method described in StackOverflow compare rows on google spreadsheets
the array equalRows contains the row numbers/indexes that contain checked checkboxes.
the OP can use the row number together with the mailData array to copy data on the relevant row.
function myFunction() {
var ws = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Promos")
var data = ws.getRange("A3:T" + ws.getLastRow()).getValues();
// Logger.log(data) // DEBUG
var mailData = data.filter(function(r){return r[1] == true })
// Logger.log(mailData) // DEBUG
// compare rows on google spreadsheets
// https://stackoverflow.com/a/34960630/1330560
var equalRows = [];
for(var i in data){
var anEqualRow = false;
for(var j in mailData){
if(data[i].join() == mailData[j].join()){
anEqualRow = true;
}
}
if(anEqualRow){
var row = +i+3
equalRows.push(row);
}
}
// Logger.log(equalRows) // DEBUG
}
Is there away to get google form (response) submit time and update time?
I have created google form and responses are being recorded in Google sheet. As soon as response is submitted it automatically captures time under first column that is "Timestamp". Now, if the response is updated ( for an example after three hours) then the time recorded initially (Timestamp) also gets updated. By the way i have created script which generates link to edit response in last column so that users can update their responses anytime. Is there any work around to get response submitted (created) time and update in different columns?
Any help is appreciated.
As suggested by Ruben,
I've created below script but it is not working
function assignEditUrls() {
var form = FormApp.openById('1UoHiwgl2Kw6RK5c7-
0kp1iFP0CPPR_LDbvSm1hw9xLg');
var sheet =
SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Form
responses 1');
var data = sheet.getDataRange().getValues();
var urlCol = 16;
var responses = form.getResponses();
var timestamps = [], urls = [], resultUrls = [];
for (var i = 0; i < responses.length; i++) {
timestamps.push(responses[i].getTimestamp().setMilliseconds(0));
urls.push(responses[i].getEditResponseUrl());
}
for (var j = 1; j < data.length; j++) {
resultUrls.push([data[j][0]?urls[timestamps.indexOf(data[j]
[0].setMilliseconds(0))]:'']);
}
sheet.getRange(2, urlCol,
resultUrls.length).setValues(resultUrls);
{
var blankRow=sheet.getLastRow(); //identify the next blank row
// date function to update the current date and time as
submittted on
var value = sheet.getRange(blankRow, 19).setValue(new
Date()).setNumberFormat('dd-mm-yyyy h:mm:ss'); //Submitted On
sheet.getSheetValues(value);
}
}
It is updating time in the last column, but if I update the form response then again it gets updated. I want create date which should be entered in the initial entry. Please help me with the correction in my script.
There is no built-in way to do that.
One option is that you use your script that adds the URL to edit the form response to also add the timestamp to another column when a new form response is submitted and to another column when the form response is edited.
Related
How to check if current submission is editing response or a new response
I am fairly new to appscript and only did a little coding in college. Here is my issue:
I am trying to filter data from one sheet into two separate existing sheets. The existing sheets already have data and formulas in it. Also, the filtered data needs to update onEdit and filtered the data into the first row that does not contain text. Another caveat is that I still need to be able to manually enter data into the cells as well from time to time and I need the function to realize this and not overwrite the manually entered data.
We are using QR codes to import data for inventory and the imported data needs to be filtered. But, we need to be able to manually input new line items into these sheets still for inventory that is non related to existing QR codes.
The function will run and filter the data correctly for the most part, but the row spacing is all over the place and the first cell, typically throws a #REF error and then continues on with the function.
At this point, I'm pretty stuck.
(I know that it will need to become an onEdit(e) function later, but just need the code to work first)
Here is my function:
function myfunction(){
var ss = SpreadsheetApp;
var orderlog = ss.getActiveSpreadsheet().getSheetByName("Order Log - COMM");
var scanit = ss.getActiveSpreadsheet().getSheetByName("Scan-It Data");
var mfg = ss.getActiveSpreadsheet().getSheetByName("Order Log - MFG");
var scanlastRow = scanit.getLastRow();
var commlastrow = orderlog.getLastRow();
// For loop to filter Vendor
for(var i=1;i <= scanlastRow;i++){
// Grab the Value from the Scan-It Data Sheet
var workingcell = scanit.getRange(1 + i, 2 );
var val = workingcell.getValue();
// If the Value equals IMMCO, send it to the MFG Sheet
// If the Value is anything else, send it to the COMM Sheet
if (val == "IMMCO") {
// Grab the Scan-It Data and send it to the MFG sheet
var scanitrange = scanit.getRange(1 + i, 1 );
//var mfglastrow = mfg.getLastRow();
//mfg.insertRowAfter(mfglastrow);
var mfgrow = mfg.getRange(71 + i, 1 );
scanitrange.copyTo(mfgrow);
} else {
//Grab the Scan-It Data and send it to the last row of the COMM Sheet
var scanitrange = scanit.getRange(1 + i, 1);
var orderlogrow = orderlog.getRange(328 + i, 1 );
//orderlog.insertRowAfter(commlastrow);
//var commrange = orderlog.getRange("A327:A"+(commlastrow+1));
scanitrange.copyTo(orderlogrow);
}
}
}
I would recommend that you use a tried and tested solution such as the moveRowsFromSpreadsheetToSpreadsheet_ script. The code is almost 800 lines long so it is impractical to quote here, but it is quite simple to configure using the parameters section.
To explain the larger context: there are several forms which generate different sheets. I'm looking for a way to conditionally copy some of the responses sheet to a seperate "Overview" document. Code-wise, I had some ideas for the Overview document, but stranded near the start.
My method was going to be to build functions for all the information I want to retrieve, such as date of birth (example in code block below), date of submission and phone number, when I click on a button. The information may only be copied if the first and surname match the ones in the Overview. The order of the sheets in different docs are not the same and the column length is continually in flux. Furthermore, the amount of rows in the Overview doc is different than the form submission sheets.
In other words: if Anne Annenson would be the twenty-first respondent to a form, I want that information in the overview sheet where they are the first person.
function getDobs() {
var targetSpreadsheet = SpreadsheetApp.getActive();
var targetSheet = targetSpreadsheet.getSheetByName("Overview");
var targetFirstNameCheck = targetSpreadsheet.getRange("A4:A");
var targetSurnameCheck = targetSpreadsheet.getRange("B4:B");
var sourceSpreadsheetDob = SpreadsheetApp.openById("...");
var sourceDob = sourceSpreadsheetDob.getSheetByName("Form responses 1");
var sourceFirstNameCheckDob = sourceSheetDob.getRange("C2:C");
var sourceSurnameCheckDob = sourceSheetDob.getRange("D2:D");
var sourceRangeDob = sourceSheetDobConsent.getRange("E2:E");
if (sourceFirstNameCheckDob==targetFirstNameCheck && sourceSurnameCheckDob==targetSurnameCheck){ //then I want to copy the data
var sourceData = sourceRangePronouns.getValues();
var targetRangeDob = targetSheet.getRange("C4:C");
}
else (//I want it to leave the cells alone, so any text or formatting that might have been put in manually is still there.){
}
}
I would like for the responses to remain in the form response sheets as well.
Any thoughts?
Cooper already explained all the things you need in the comments. And below is what your code would look like following Cooper's comments.
Code
function getDobs() {
var targetSpreadsheet = SpreadsheetApp.getActive();
var targetSheet = targetSpreadsheet.getSheetByName("Overview");
var targetLastRow = targetSheet.getLastRow();
// range equivalent to A4:B
var targetNamesCheck = targetSheet.getRange(4, 1, targetLastRow - 3, 2).getValues();
// tested in same spreadsheet, change "targetSpreadsheet" to openById on your actual script
var sourceSpreadsheetDob = targetSpreadsheet;
var sourceDob = sourceSpreadsheetDob.getSheetByName("Form responses 1");
var sourceLastRow = sourceDob.getLastRow();
// range equivalent to C2:D
var sourceNamesCheckDob = sourceDob.getRange(2, 3, sourceLastRow - 1, 2).getValues();
// range for data to be copied (E2:G in my sample data)
var sourceRangeDob = sourceDob.getRange(2, 5, sourceLastRow - 1, 3).getValues();
var output = [];
targetNamesCheck.forEach(function (targetNames) {
// search sourceNamesCheckDob for targetNames
var index = searchForArray(sourceNamesCheckDob, targetNames);
// if targetNames is in sourceNamesCheckDob, save the data on that row for later
if (index > -1)
output.push(sourceRangeDob[index]);
// append blank cells if data is not found
else
output.push(new Array(sourceRangeDob[0].length));
});
// if there were names that were found, write the data beside the targetNames
if (output.length > 0) {
targetSheet.getRange(4, 3, output.length, output[0].length).setValues(output);
}
}
// function to search the array for the object
function searchForArray(haystack, needle) {
var i, j, current;
for(i = 0; i < haystack.length; ++i) {
if(needle.length === haystack[i].length) {
current = haystack[i];
for(j = 0; j < needle.length && needle[j] === current[j]; ++j);
if(j === needle.length)
return i;
}
}
return -1;
}
Overview:
Form responses 1:
Overview after running getDobs:
EDIT:
Since there are no methods that includes the apostrophe when the cell value is being fetched, easiest way is to have the sheets identify the phone number as text so it won't remove the 0 in the beginning. I've thought of 3 ways to have the 0 included in the output:
Add the apostrophe manually on the specific output column via script
Add dashes on the number so it is treated as text (09395398314 -> 093-9539-8314) (just an example, not sure if that is the right format)
Format the output column into number -> plain text instead of number -> automatic
I prefer formatting the output column as that will be the fastest and easiest thing to do.
Format:
Output:
Note:
This function will fill up rows where names in Overview are present in Form responses 1.
References:
Check whether an array exists in an array of arrays?
javascript create empty array of a given size
I am working with google forms via app script. I want to determine if the current form that is being submitted is in edit mode or a new response? How can I check this in the onSubmit event.
If yes that user is edit a previously submitted response than I want to change the value in my spread sheet to "yes".
below is a snippet of my code:
function testExcel2() {
var email = "email";
var s = SpreadsheetApp.openById("id");
var sheet = s.getSheets()[0];
var headers = sheet.getRange(1,1,1,sheet.getLastColumn() - 1).getValues()[0];
var datarow = sheet.getRange(sheet.getLastRow(),1,1,sheet.getLastColumn() - 1).getValues()[0];
var message = "";
for(var i in headers)
{
message += "" + headers[i] + " : " + datarow[i] + "\n\n";
}
MailApp.sendEmail(email, "Submitted Data Test", message);
var af = FormApp.getActiveForm();
//af.setCustomClosedFormMessage("The form is currently processing a submission, please refresh the page.");
af.setConfirmationMessage('Thanks for responding!')
//af.setAcceptingResponses(false);
var rowKey = "o" + sheet.getLastRow();
var editCell = sheet.getRange(rowKey).setValue('no');
}
The Google Form form submit event doesn't have a field that could help to know if the response is a new response or if it's a response edit. As Sandy already said through question comments, the Form Servicer classes and methods, nor the response values include something that could help on this.
By the other hand, the Google Sheets submit event has a range field that could help. The following script bounded to a Google spreadsheet logs the response row:
function onFormSubmit(e){
var response = e.range;
Logger.log(response.getRow());
}
The above could be used to keep updated a column to hold a revision counter. If the corresponding cell is blank, then the response is a new response, other way it's a response edit.
The following script it to be bounded to the spreadsheet that receives the Form responses. It requires a on form submit installable trigger. Other instructions to adapt it are included on the script comments.
/*
*
* Global Variables
*
*/
/*
* Sheet name used as destination of the form responses
*/
var sheetName = 'Form Responses';
/*
* Name of the column to be used to hold the response revision counter
* It should match exactly the header of the related column,
* otherwise it will do nothing.
*/
var revisionsColumn = 'Rev';
/*
* Responses starting row
*/
var startRow = 2;
function setRevisionCounts(e){
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(sheetName);
var headers = sheet.getRange(1, 1, 1, sheet.getLastColumn()).getValues();
var revisionsIndex = headers[0].indexOf(revisionsColumn);
var data = sheet.getDataRange().getValues();
var response = e.range;
var rowIndex = response.getRow()-1;
var rev = data[rowIndex][revisionsIndex]+1;
sheet.getRange(rowIndex+1, revisionsIndex+1).setValue(rev);
}
This is not really an answer but rather a 'heads up'. I have found that if you resubmit (edit) a form that data that is not changed is resent unchanged.
Why does this matter? Let's say you have a branch in your form eg Do you like apples or bananas? If you like apples then the next question might be what is the colour of your car? Let's say 'red' of if you like bananas the next question might be how old are you? Let's say 35.
So you choose apples and red and submit the form.
Then you edit your reply and instead choose bananas and 35.
When the form is resubmitted the new data still says car=red and (as expected) age=35.
I would expect/hope that now car=null and age=35 but this is not the case. Car=red still appears against the updated row in the destination sheet.
I hope this is useful to you and maybe someone will suggest a work around.
You can compare the response length vs row length(or the number of columns in the row). Unless the submissions edits/changes every field/response then you know it should be less than the total number of questions.
function onFormSubmit(e) {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet()
const response = (e.range)
const responseValues = response.getValues()
const responseRow = Number(response.getRow())
const rowValues = sheet.getRange(responseRow, 1, 1, sheet.getLastColumn()).getValues()
if(responseValues.length < rowValues.length){
//edited submission
}else{
//likely new submission
}
Amateur here from Excel
ugly but works and this is a simple version - looks for notes - selects that row, updates values on second sheet and deletes the notes.
function mody() {
const sh = SpreadsheetApp.getActiveSpreadsheet();
const mn = sh.getSheetByName('Form Responses 1');
const puty = sh.getSheetByName('putter');
var lst = mn.getLastRow();
var dd = puty.getLastRow();
dd=dd+1
//replace row with edited form response if any
for (var z = 2;z< lst+1;z++){
var nts = mn.getRange(z,2,1,6).getNotes()
var ttt = ((nts.join()).length)
if (ttt>5){
mn.getRange(z,2,1,6).clearNote() //removes the notes on the modified
row
dd = z
lst = z
}
}
puty.getRange(dd,1,1,5).setValues(mn.getRange(lst,1,1,5).getValues());
}