Copy last row to another spreadsheet upon form submission - google-apps-script

I have a Google spreadsheet with an Add-on that takes data from a form and runs on form submission. I also have another Add-on that pushes the data from this spreadsheet to another spreadsheet - let's call it spreadheet2 here. In spreadsheet2 I have my own script with a function copyLastRow() that copies the last row from this spreadsheet to another spreadsheet - let's call it spreadsheet3. My script is supposed to append a new row from spreadsheet2 to spreadsheet3. It runs OK when I run it manually, but it is not running via the project trigger - which I installed for Script editor's Resources - I tried both on Edit and on Change triggers, but they are simply not firing up when data is pushed from spreadsheet2. The script is working when I actually edit spreadsheet2. However, this is not good for what I need - I really need the script to work without manual intervention. Can you, please, help?
function copyLastRow() {
var target = SpreadsheetApp.openById('xxxxxxxxx').getSheetByName('Sheet1');
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Sheet1');
var lastrow = sheet.getLastRow();
var sourceData = sheet.getRange(lastrow, 1, 1, 53).getValues();
target.appendRow(sourceData[0]);
EDIT: I updated the code - I realized I left the previous version of code here.

You're right to worry about whether this function will be effective when multiple users are submitting forms... it won't be. But it's easily improved.
What's the problem? When copyLastRow() runs, it assumes that the last row of the source spreadsheet contains the response that also triggered the function. However, before it gets around to reading that row, another user might submit a form. (Eventually, the function will be triggered by that submission as well, and could process the same row a second time.)
The simplest improvement in this situation is to take advantage of the event object that is provided to the trigger function as a parameter. See Google Sheet Events for some background details.
The newly submitted responses are in event.values, which is an array - exactly what is needed for .appendRow(). Here's how we can update your copyLastRow function:
function copyLastRow(event) {
var target = SpreadsheetApp.openById('xxxxxxxxx').getSheetByName('Sheet1');
target.appendRow(event.values);
}
Now it doesn't matter how many users submit forms - each will be handled uniquely by this function.

Related

How to check if user is actively editing a cell

I have a very simple script that is triggered every 15 minutes to re-alphabetize a sheet based on that day's date. It works perfectly, except for one issue. The sheet is used by about 8-10 users at any given time. If a user is actively typing in a cell when the function is triggered and the sheet is resorted, then when they finish typing the cell they have edited the wrong cell. For example, if they start editing cell D24, and then after it is resorted that row becomes D28, then when they hit enter they will overwrite the new D24.
Is there any way I could incorporate something in the code to check if there are any cells that are actively being edited (greyed out because someone is inside it)? Or some other solution that would solve this issue?
function Alphabetize() {
var spreadsheet = SpreadsheetApp.getActive()
var now = new Date();
var name = Utilities.formatDate(now,"EST","MM-dd")
var day = now.getDay()
var sheet = spreadsheet.getSheetByName(name)
var col = sheet.getLastColumn()
var row = sheet.getLastRow()
var range = sheet.getRange(3, 1, row, col)
range.sort({column: 1, ascending: true})
}
Since modals don't work in this situation (time based triggers and other users) I figured out a slightly clunky work around. I added a row at the top of the sheet in the frozen section that is bright yellow and says "Press Enter. Sheet about to re-sort." multiple times so it's visible across the entire sheet. I then hid that row and inserted this code right before it sorts:
sheet.showRows(2)
Utilities.sleep(10000)
So it shows that row, waits for 10 seconds, sorts and then hides the row again with sheet.hideRows(2). Elegant, it is not. But it works on the time trigger and every user sees it. If there's a more elegant solution, I'm all ears.
Is there any way I could incorporate something in the code to check if there are any cells that are actively being edited (greyed out because someone is inside it)?
I think that this could be possible by using a web browser extension that reads the spreadsheet DOM but it will not very dependable as the web page source of many Google apps are generate automatically and anything could change without previous notice.
By one hand, the status identified by the web browser extension should be saved on a document / script store by using the Properties Service or the Cache Service so the time-driven trigger could read the status and by the other, the time-driven trigger save a flag to warn the user using the same services so the web browser could read them.
NOTE: In order to make this work, the web browser should call functions from the same project of the functions ran by the time-driver trigger.
By using Google Apps Script you could use spreadsheet.toast(...) to show a modeles and non so intrusive warning, then use a modal dialog to interrupt the user.

Google Scripts onEdit not recognising data being edited on a sheet during a sync from a mobile device

I have a Google sheet that is updated by a mobile app created on AppSheet.
I have a column of data that I need to keep a history of so wrote a script to copy the column to a fblank column in another sheet.
function readdailyChecks() {
var sheetFrom = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Todays Checks");
var sheetTo = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("History");
// Copy from 5th column, all rows for one column
var valuesToCopy = sheetFrom.getRange("D2:D100").getValues()
//Paste to another sheet from first cell onwards
sheetTo.getRange(1,sheetTo.getLastColumn()+1,valuesToCopy.length,1).setValues(valuesToCopy);
}
I then wrote another script to do this task when the sheet was edited thinking the sync would edit the sheet data.
function onEdit(e) {
if(e);
readdailyChecks(e)
}
The idea being that the app would sync once the form was completed, update the sheet and trigger the onEdit code to do it's stuff.
The problem is that the sync changes the sheets data without editing it so the historical data is not created!
Is there an onSync code or a way that when the data changes the script can be triggered?
I work at AppSheet. When updates are made to Google sheets via the Google Sheets backend API, they do not fire the onEdit trigger. I'm not sure why exactly this is, but it is a limitation imposed by Google. So that is what you are observing.
The AppSheet documentation suggests that you try a timed trigger instead, polling for changes. https://appsheethelp.zendesk.com/hc/en-us/articles/206483017-Google-Drive
Not the greatest, but it does work. Some AppSheet users have reported success with the onChange trigger instead of the onEdit trigger. To me, this defies logic based on the documented meaning on an onChange trigger, but it appears to work for these users, so worth a shot.

Duplicate Form responses to two Google Sheets programmatically

I need to save my Google Form in two different sheets.
The first sheet will be the one for history and the other one will be exploited by the logistic services (who may delete some rows when the clients receive the shipped stuff).
I really need to keep all the responses on the first sheet whatever the logistic services do on the second.
The fact is; I was using formRat, but is not working anymore and I don't see any complementary module that does exactly the same thing. I'm not good enough in programming to write the script by myself.
I tried to write this in the second sheet:
=ArrayFormula('first_sheet_name'!A:W)
But when I try to delete a row on the second sheet, it reappears a few seconds later because Google Sheets recalculates it.
A form submission trigger script attached to the Form Response spreadsheet can easily copy responses to the second sheet, as they arrive. Any modifications made later on the second sheet will survive.
Here is a very simple example of such an Installable Trigger Function. You need to declare ss2ID with the Sheet ID of spreadsheet 2. The script assumes that the responses are to be copied to the first sheet in spreadsheet 2, and that all form answers are populated.
function copyResponse( event ) {
fixFormEvent( event ); // From https://stackoverflow.com/a/26975968/1677912
var ss2Id = "---sheet-id---";
var sheet2 = SpreadsheetApp.openById( secondSheetId ).getSheets()[0];
sheet2.appendRow( [event.values] );
}
This function uses fixFormEvent( event ) from e.values in google forms skips empty answers, is there a workaround? to ensure the columns in the new sheet align with the original questions.

Getting latest Form Response sometimes gets the one before it instead

I'm wondering if this has to do with particularly busy times for Google Apps Script, because it seems like it has to do with an (occasional) delay in updating the length of a formResponse[] array. I'm using the following code to get the latest response triggered by a form submit:
var form = FormApp.getActiveForm();
var formResponses = form.getResponses();
var formResponse = formResponses[formResponses.length-1]; //latest response only
Logger.log('begin length: ' + formResponses.length);
Then the rest of my script interacts with the answers in the formResponse[] array. Occasionally, I'll notice that it has gotten the response before the latest response. I can verify this because the spreadsheet with the form responses shows the actual latest response. My script takes 5-15 seconds to execute, so I I have the following lines at the end of my code to double check the length of the array again:
var form2 = FormApp.getActiveForm();
var formResponses2 = form2.getResponses();
Logger.log('end length: ' + formResponses2.length);
and in the log I'll notice that the second one is one greater than the first (and the second one is the correct value). I haven't really found much pattern as to when it happens, but it seems to happen more often between the hours of 7-9am PST. For now I've added a Utilities.sleep(5000) as the first line in the function to allow time for the form to update before I get the responses, and so far I haven't had any n-2 responses, which makes me think there is some sort of delay causing the form to record the latest response after the "on form submit" trigger fires.
Has anyone else encountered anything similar?
When servers are busy, these race conditions will become more pronounced, but they are simply business as usual for cloud computing. In a nutshell, each user of a document has a view (copy) of that document, which cannot be guaranteed to be identical to a "master version" all the time. When you look at a spreadsheet, you are looking at your own copy of that spreadsheet. Your collaborator might be looking at their own copy. A trigger function accessing "the spreadsheet" will, in fact, be given its own copy as well. Changes made anywhere need to be synchronized everywhere, and that takes time.
In this case your code indicates that you have a function in a script that is contained in a Google Form. That script, when triggered, will be given a copy of the Form, including past responses. However, the form submission that triggered the script may not be synchronized with the form submission yet. You're also working with a spreadsheet that contains responses... when forms are submitted to the Form Service, they are stored in the Forms Service and they are also stored in a copy of the spreadsheet. That action may trigger a Spreadsheet Form Submission event, and (is your head sore yet?) that function will be given a copy of the spreadsheet that might not yet contain the new form submission data!
So, what to do?
Let's assume you're using a trigger function to handle form responses.
function handleForm( event ) {
...
}
If you only need to process the "current form response", you should use the event information that is handed to the trigger, rather than reading the spreadsheet or querying the form responses. Read over Understanding Events to see what event information is provided to the specific type of trigger you're dealing with. (Added bonus: using the event parameter saves you from calling the services APIs, which makes your function faster.)
function handleForm( event ) {
var formResponse = event.response; // The response that triggered this
// function is in the event
...
}
I suggest you also take a look at "How can I test a trigger function in GAS?".

script to force republish of google spreadsheet

I have created a form that pushes data to a Google Spreadsheet. The data is latitude, longitude, location, and other identifying data. The spreadsheet is then published as a .CSV file and imported into ARC GIS to be displayed on an interactive map. It works exactly as I wanted and I set it to republish after each change.
The problem is that when the spreadsheet has rows appended by the script, it is not seeing it as a change and republishing. In order to get the updated data imported to the map, I need to go in and manually republish. Is there anyway through the Google Apps Script that I could make a few lines of code to force a republish? I could then add that to the "on form submit" script I have or another time based one that already runs at 3 am everyday.
I have looked through the Google Apps Script documents and not found anything. When searching for help on the web, the overwhelming majority of responses are for how to publish your script as a template for other.
My testing sheet was republished after the following function was executed by either a menu entry or a time-based trigger.
function ChangeIt() {
var sheet = SpreadsheetApp.getActiveSpreadsheet()
var t = new Date()
var x = 'upd: ' + t
var range = sheet.getRange('a3')
range.setValue(x)
}
If I were in your shoes, I'd add an extra column to the end of the sheet with some benign constant data that a script can change without affecting the systems consuming the data. If an extra column isn't an option, try modifying my sample to read in a current value, change it, and immediately change it back.
Also, I'd see if the spreadsheet onEdit() trigger fires when the form submit adds a new row. If so, tie your GAS function to it to force the republish. If not, setup a timed trigger to execute the GAS function.
A quick workaround for this issue that doesn't require scripting is to simply make an array copy of the data.
For example, I made a new tab and in A1 put this: =ArrayFormula('Form Responses 1'!A1:Z1000)
While the main Form responses tab will insert rows and not play nice with formulas this new tab stay nice and constant and updates automatically when new data is added.