Insert new row and move data based on cell text - google-apps-script

Trying to automate a Google sheet to save time for a team member. I have zero scripting experience. I am trying to insert a row based on a "Yes" result from column K in a Google form submission sheet and then move data in cells L:P to the new row, all without messing up the query functions that are pulling this data.
Is this possible?

Appending selected data from a form submission
function onFormSubmit(e) {
var ss=SpreadsheetApp.openById('SpreadsheetID');
var sh=ss.getSheetByName('Sheet Name')
if(e.values[11]=="Yes") {
var nV=e.values.slice(11,16);
ss.appendRow(nV);
}
}
Since you said that you have zero scripting experience, I should warn you that you cannot run this function without supplying the event object and personally I would never append new rows to a linked sheet. I would append the rows to another sheet instead. There have been problems with the onFormSubmit trigger lately which causes spurious additional triggers as described here.

Related

Trigger bug when copying rows

I have a function that copies rows from one sheet to another, which works when I run it manually:
function updateDataRange() {
var formSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Form Responses 1");
var lastFormRow = formSheet.getLastRow();
var dataSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("DataRange");
dataSheet.clear();
for(var rowCounter = 1; rowCounter <= lastFormRow; rowCounter++) {
var sourceRange = formSheet.getRange('B'+rowCounter+':H'+rowCounter);
var insertRow = dataSheet.getLastRow() + 1;
var targetRange = dataSheet.getRange('A'+insertRow);
sourceRange.copyTo(targetRange);
}
}
However, when I run this via a trigger (e.g. function onEdit(e) { updateDateRange(); } ), there are gaps in the rows and some of the values will be left out. In this case, I know there is a "fix" by using rowCounter instead of insertRow to write the files instead of using getLastRow(), but one can easily see how this is a problem in another scenario.
So I guess my question is simple: Why isn't this working correctly when using a trigger?
Edit: To clarify, I have a 3rd sheet with some conditions/cells (i.e. conditions that influence which rows are to be copied) and changing them (like changing a different date) trigger the function.
Explanation:
You are using an onEdit trigger to capture sheet changes. But onEdit triggers work only when the user changes the value of a cell. Referencing the official documentation:
The onEdit(e) trigger runs automatically when a user changes the value
of any cell in a spreadsheet.
In your case, I think you are trying to execute this code when the Form Responses sheet is filled in with data by the form.
There are two kind of trigger functions that work with form submissions and they are both installable.
One is a google sheet event based trigger and the other one a form event based trigger.
Your goal is to execute some code from the google sheets side, so it makes sense to use the first one.
Modifications:
Change the name of the function from onEdit to a different name of your choice e.g. myFormSubmitTrigger.
Since the trigger is installable you need to "install" a onFormSubmit trigger for the myFormSubmitTrigger function. Here you can find some simple instructions on how to do that.
If your question is why doesn't your function work when the form makes changes to sheet Form Responses 1 then Marios has answered you question accept it and move on. If your trying to run your function when a user is making edits to sheet Form Responses 1 using a simple trigger then a possible explanation for not getting all of the rows completely copied is that simple trigger must complete in 30 seconds. If you continue to accept more data in Form 1 Responses then soon or later you will have problems but with this function it will be a lot later because it will run much faster than your code:
function updateDataRange() {
const sss=SpreadsheetApp.openById('ssid');
const ssh=e.source.getSheetByName('Form Responses 1');
const sr=2;
const svs=ssh.getRange(sr,2,sh.getLastRow()-ssr+1,7).getValues();
const dsh=e.source.getSheetByName('DataRange');
dsh.clear();
dsh.getRange(1,1,svs.length,svs[0].length).setValues(svs);
}
However, I would recommend that you never edit a form responses sheet. I would consider using the onFormSubmit trigger to capture all of the data to a second sheet that is available to edit. And it will have additional data automatically appended to it via dsh.appendRow(e.values). And so now you would no longer require an onEdit trigger because your data sheet is kept upto data with an onFormSubmit trigger and you may feel free to edit the datasheet any time you wish.
If neither of these questions suffices then I would recommend that you be more clear about what you are asking.

Is there a way to auto save data in google sheet?

Is there a way to auto save data entered in a temp area (risk is a calculated value based on the values entered) on google sheet. I have a working space and all my logs is now needing to be saved for later review.
see sample sheet.
Created a sample data screenshot
Thanks
There's two ways to do it. You'll need to create a log of sorts and have the dashboard reference the bottom most entry. If you have App Script experience, that would be the better solution, however without it you could use the a Google Form for editing the dashboard. There wouldn't be any formulas alone that will work for this due to needing to hardcode the inputs, and formulas can only return values as arrays (mirror/change values in other cells).
You can use a Google Form that is linked to the spreadsheet so that someone has to submit the form with the inputs to change the dashboard. You would then use a =Max() function on the timestamp column, and then either Vlookup or Index(match()) to return the variables for the dashboard based off Max(timestamp).
The alternative method would be to create basically set of cells similar to the input table, and add a button that if clicked, takes, the values and updates them in the variables for the dashboard, but also logs them on another sheet. (It would be something like this)
Thank you all for the suggestions. I end up using the below script to accomplish the task.
function FormExec() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sinput = ss.getSheetByName("sheet1");
var soutput = ss.getSheetByName("sheet2");
var input = sinput.getRange(14, 3, 15).getValues();
var flatin = [].concat.apply([], input);
soutput.getRange(soutput.getLastRow()+1, 1,1, 15).setValues([flatin]);
soutput.insertRowAfter(soutput.getLastRow());
Logger.log(input);
}

Using "=TRANSPOSE()" for my column headers, how do I keep data linked when I insert a new row in the original data?

I have one sheet which is my data in which I list my events and their dates. These are then inserted on a separate sheet where volunteers can use checkboxes for their availability. Now if I reorder my events in the data-sheet then the headers in the availability sheet will move, but the availability will not move with them. Any way to fix this?
Here is an example sheet:
https://docs.google.com/spreadsheets/d/1tTVMOCKnLT2dRhKDBMV74LNMYOQvxKv3AA8egHCVG78/edit?usp=sharing
This is a simplified example from my actual problem, but I was wondering if there is a good way to keep this data linked? I would like to be able to re-order the data in the "Events Data" sheet while keeping the correct availability under each event in the "Availability sheet". Currently, one moves and the other one is static.
How I've done this using Google Apps Script in a similar situation (in my case, associating a "memo" column with rows producted by QUERY). Essentially, what we do is synthesize our dynamic display (with QUERY in my case and TRANSPOSE in yours) from static sources (other sheets), and use Google Apps Script to move data entered on the dynamic sheet to the appropriate static sheet, where we can easily retrieve it and render it appropriately.
The only information stored on the dynamic sheet is that used for rendering it (TRANSPOSE, VLOOKUP, whatever works for your situation). When the user edits that sheet, we will take the value of their edit and immediately move it to a static sheet using onEdit(). This information from the static sheet will then be rendered onto the dynamic sheet, regardless of how its rendering changes.
First, create a sheet where you will store the true values for each row.
Second, use an arrayformula vlookup (or hlookup) to search your storage sheet based on a unique identifier from your transposed sheet, e.g.
=ARRAYFORMULA(IFNA(VLOOKUP(A2:A, Storage!A2:B, 2, 0), ""))
Third, using the Script Editor, add the following to your onEdit() function in Google Apps Script:
if (/* use e.range here to check that you're in an appropriate location... */) {
//this section should execute on our 'dynamic' sheet, after the user edits it
var unique_id = SpreadsheetApp.getActiveSheet().getRange(e.range.getRow(), 1).getValue();
// the 'getRange' here is looking from where the user edited to a unique identifier
// associated with this record; this could potentially be built on to reference multiple unique identifiers.
// For instance, this would be the cell with the text, e.g., unique_id = 'Event A'
// and the script could be extended to ALSO pick up, e.g. "edited_person", etc.
var storagesheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Storage');
var findid = storagesheet.createTextFinder(unique_id).findNext();
// this will search your storage sheet to see if there is an existing entry associated with the unique ID
if (findid) {
storagesheet.getRange(findid.getRow(), 2, 1, 2).setValues([[e.value, e.user]]);
// if there is, then we update it ON THE STORAGE SHEET with the new contents of the cell
} else {
storagesheet.appendRow([unique_id, e.value, e.user]);
// otherwise, we create one with the contents of the cell.
//This can likewise be expanded; create more cells, update more sheets, create new sheets, whatever else may be needed
}
e.range.clearContent();
if (e.range.getRow() === 2) { //or wherever your array formula is, again we are assuming this part of the onEdit() should only occur for edits of the dynamic sheet
//making sure we don't lose array formula if the user writes in this field
e.range.setFormula("=ARRAYFORMULA(IFNA(VLOOKUP(A2:A, Storage!A2:B, 2, 0), \"\"))")
}
}
These are based on vertically stored/displayed information, and only store a single field for each unique ID, but you can extrapolate on that pretty easily to match based on both the event and person's value, store multiple values for each event, etc. according to your needs.

How can an apps-script on a Form store extra data into the Sheet?

Q: How can an AppsScript attached to a Form store an extra piece of data into the Sheet?
Situation: We have a (long) Google Form that stores many pieces of data into a Google Sheet. Often the entries need to be edited, and it is much easier to edit using the original form than trying to edit directly into the sheet. (Some of the items are text, several paragraphs long.) I would like to store into the spreadsheet one additional piece of data, specifically the URL that an editor can use to edit the row entry using the form.
I can already get all the form data and I can get the right URL with formResponse.getEditResponseUrl(). And I can send all of that in an email to a user, usually the editor who is collecting all the form entries. (Thanks to many helpful answers in StackOverflow for getting me this far!) But the editor has to manually copy and paste the URL into an additional column in the proper row of the spreadsheet.
I see an interface in class Sheet to add a column to the spreadsheet, but I don't see how to populate that extra column for the particular row that the form just stored. We have added the column manually, and have verified that it is not overwritten by Google when editing via the form. How do I store that one little piece of data into the sheet?
What am I missing? Any help will be greatly appreciated. Thanks.
[added clarifications 2015-02-06]
We have a long form that some people submit and other people edit. Editing is to be done using the form, not editing directly in the spreadsheet, so we need the URL that permits the editors to re-edit the response.
I would like to store that URL into the spreadsheet during the form submission, so that the editors, who have access to the sheet, can find it.
In a script on the Form side, I can easily calculate that URL, but now how do I store it into the sheet in an extra column?
In my Form-side script at the moment, I get the URL and send it, along with all the form data, in an email to the editors' distribution list. One of the editors then copies the URL from the email and pastes it into the sheet. (Most of the time, into the correct row, even. :-) This is a potentially error-prone manual step.)
A secondary question: what is up with the row numbers in the sheet versus the response numbers in the form.getResponses()? The row numbers and response numbers seem to wander as new items are submitted (i.e., new rows), and old items are edited. Can one reasonably predict the sheet's row number in which the editor will find the form data?
Again, thanks for any help you can give me on this. We have a survivable interim solution. However, with a hundred or so form entries coming in the next couple months, I would love to error-proof the process as much as possible.
rick
So, I've just stumbled upon your questions and, hopefully, I've understood it correctly.
Possible problems:
the script is incorrectly bound to the spreadsheet attached to the form and not to the form itself (which is not the problem in your case as far as I understood from your description)
race conditions between submission insertion and additional column edit, or between simultaneous submissions (see lines 27-32 from code)
accessing the spreadsheet directly, without prior selecting a sheet from the spreadsheet, even if it spreadsheet contains only one sheet! (see lines 36-37 from code)
using the column numeric index, instead of the corresponding column letter as argument for getRange() method, which accepts only column letters AFAIK (see lines 42-43 from code)
Below you have the code which should address all these problems (I have not tested it, but it is an adaptation of a perfect working solution for a very similar scenario):
// Converts sheet column numeric index to corresponding column letter
function columnToLetter(column)
{
var temp, letter = '';
while (column > 0)
{
temp = (column - 1) % 26;
letter = String.fromCharCode(temp + 65) + letter;
column = (column - temp - 1) / 26;
}
return letter;
}
The following function must be registered to an "On form submit" event from form - not from the spreadsheet! (Script Toolbar -> Resources -> Current project's triggers -> Add a new trigger)
// Associated the sheet rows with response URLs in an additional column
function onFormSubmit(e)
{
try
{
// Get the response Url, either from FormApp:
var responseUrl = FormApp.getActiveForm().getEditResponseUrl();
// Or alternatively get it from the event:
// var responseUrl e.response.getId().getEditResponseUrl();
// ....................
// Other URL processing
// ....................
// Get a public lock on this script, because we're about to modify a shared resource.
var lock = LockService.getPublicLock();
// Wait for up to 30 seconds for other processes to finish.
lock.waitLock(30000);
// Wait for row insertion to finish, so that sheet.getLastRow() method gets the updated number of rows
Utilities.sleep(1000); // 1 second
// Here insert the URL to your spreadsheet
var spreadsheetUrl = "https://docs.google.com/spreadsheets/d/YGUgHi28_gYUffGYGGH_78hkO1Pk/edit";
// Gets the first sheet inside the spreadsheet (if you have multiple sheets, just change the value [0])
var sheet = SpreadsheetApp.openByUrl(spreadsheetUrl).getSheets()[0];
// Get updated number of rows and columns, after form submit inserted the new row
var lastRow = sheet.getLastRow();
var lastColumn = sheet.getLastColumn();
// Get the exact cell, next to the right of the new row, by converting the column index to corresponding letter
var lastCell = columnToLetter(lastColumn) + lastRow.toString();
// Set the content of the cell with the new URL
sheet.getRange(lastCell).setValue(responseUrl);
// Release the lock so that other processes can continue.
lock.releaseLock();
}
catch (error)
{
// If there's an error, show the error message
return error.toString();
}
}
For any other questions, just write a comment. Hope it helps.
You can use the form submit range parameter to get the row / spreadsheet range of the form data being placed in the sheet. Then use the range offset method to push your data into the column after the last column of form data.
Notice if you use the HYPERLINK formula, you must escape the quotes that are passes as parameters.
e.g.
function formProcessing(e){
var formData = e.values;
var dataRange = e.range; // gets the range on the spreadsheet
/*
do all your processing
*/
var url = "http://www.google.com"; // whatever url to put in spreadsheet
// add the url value to the spreadsheet
formRange.getCell(1,formRange.getLastColumn()).offset(0,1).setValue(url);
// or if you want a named link
//formRange.getCell(1,formRange.getLastColumn()).offset(0,1).setFormula("HYPERLINK(\"" + url + "\", \"Edit Form\")");
}

Spreadsheet Email Trigger

I have Written Script on Google Spreadsheet to send Email when spreadsheet is modified or any Data is added. Email Trigger is working but whenever any data is entered in next Row it send Email to previous email address also.
Please suggest solution
The below is written script :
function onEdit(e) {
var sheet = SpreadsheetApp.getActiveSheet();
var startRow = 2; // First row of data to process
var numRows = 1; // Number of rows to process
var dataRange = sheet.getRange(startRow, 1 , numRows,3) // Fetch the range of cells A2:B3
// Fetch values for each row in the Range.
var data = dataRange.getValues();
for (i in data) {
var row = data[i];
var emailAddress = row[2]; // First column
var message = row[0] + "requested" + row [1]; // Second column
var subject = "Sending emails from a Spreadsheet";
MailApp.sendEmail(emailAddress, subject, message);
}
}
Your question is unclear... nowhere in the script I see something that reads which cell is actually modified... your target range is hardcoded on row 2 so the only row that can be processed is row 2 (and the mail can only be sent once)...
So can you :
explain how it should work
explain how it works now , especially what do you mean by 'previous email'
remove typos in your code (row[2] is not First column)
explain how you trigger this function : the name onEdit(e) suggest an onEdit trigger but simple triggers cannot send mail so I suppose you have set some other trigger.
explain why (e) in your function parameter and not using it ?
EDIT : thanks for the complement of information.
The script you suggest is not sufficient to achieve what you want. The idea here is to check if something in the sheet has been modified either by adding (or inserting) a row of data or (if I understood well) by editing any row in the sheet with a new value.
This is not really as simple as it looks at the first glance ;-)
What I would do it to take a 'snapshot' of the sheet and -based on a timer or onEdit - compare that snapshot to the sheet's current state.
There is more than one way to get that result, you could have a second sheet in your spreadsheet that no one could modify and that is a copy of the main sheet that you update after each modification/mail send. So before updating the script should look for any difference between the sheets and send a report to the corresponding email when a difference is found.
Another way to do that is to store the sheet data converted to a string in the script properties, the principle is the same but it's more 'invisible' for normal users accessing the spreadsheet.
You could also use scriptDb or your userproperties but the script properties is probably better suited (simpler) for this use case.
Tell us what you think/prefer and I (or someone else) could probably give you some code to start with.
It appears that you're using a shared spreadsheet to collect the add-user-requests, and trusting the requesters to fill in the information. In the detail document you shared, it further appears that requests are ADDED, but not EDITED. (That's an important simplifying distinction.)
I suggest that what you really need is to use a form for receiving that input. Using a form will create a "data table" within your spreadsheet, a set of columns that you must not mess with. (You can edit the contents, add and delete rows, but must not add or remove columns.) However, you CAN add columns to the spreadsheet outside of this table, which gives you a handy place to store state information about the status of individual requests.
Further, you can trigger your processing to run on form submit, rather than a simple "onEdit" - this gets away from the problem that ScampMichael pointed out. Alternatively, you can use an installable edit trigger, as described in this answer.
Try this sheet, and this form. Save yourself a copy, go into the script and remove the comments that are stopping emails from being sent, and try it out. There's a menu item in the spreadsheet that can kick off processing; just clear the "Request State" column to re-run it. You can open the form (and find its URL), and add more entries to experiment.
It's the core of a similar system that I've written, and contains a discreet state machine for processing the requests. My system has large amounts of very complex data in multiple spreadsheets, so it often gets pre-empted, then needs to run again. (I use a timed trigger for that.) That's why requests are handled through states. If you find that too complex, pull out only the parts you need.