I have a function that is intended to detect changes in one spreadsheet and copy them into another spreadsheet in the same exact cell location if the change is made in sheet1. I first tried to do this using the installed onEdit(e) function provided by sheetsAPI, but kept running into the error that I'm not authorized to open a new spreadsheet from a built-in function. I tried setting up my own trigger, but it won't activate even though I assigned an OnEdit trigger to the function.
function ChangeDetect(e){
var range = e.range
var esheet = SpreadsheetApp.getActiveSheet()
var ss = SpreadsheetApp.getActiveSpreadsheet()
var sheet = ss.getSheets()[1];
// literally, if the edit was made in the first sheet of the spreadsheet, do the following:
if (esheet.getIndex() == ss.getSheets()[0].getIndex()){
var numrows = range.getNumRows();
var numcols = range.getNumColumns();
for (var i = 1; i <= numrows; i++) {
for (var j = 1; j <= numcols; j++) {
var currentValue = range.getCell(i,j).getValue();
var cellRow = range.getCell(i,j).getRow();
var cellCol = range.getCell(i,j).getColumn();
var ss3 = SpreadsheetApp.openByUrl('https://docs.google.com/spreadsheets/d/1K2ifzjPTATH77tV4xInh0Ga2SuPsLdgNRSbekjDx-w8/edit#gid=0')
var sheet3 = ss3.getSheets()[0];
sheet3.getRange(cellRow,cellCol).setValue(currentValue)
}
}
}
}
I think you may be experiencing the limitation/restrictions of installable triggers:
Restrictions
Because simple triggers fire automatically, without asking the user for authorization, they are subject to several restrictions:
The script must be bound to a Google Sheets, Docs, or Forms file.
They do not run if a file is opened in read-only (view or comment) mode.
They cannot access services that require authorization. For example, a simple trigger cannot send an email because the Gmail service requires authorization, but a simple trigger can translate a phrase with the -
Language service, which is anonymous.
They can modify the file they are bound to, but cannot access other files because that would require authorization.
They may or may not be able to determine the identity of the current user, depending on a complex set of security restrictions.
They cannot run for longer than 30 seconds.
In certain circumstances, add-ons for Google Sheets, Docs, and Forms run - their onOpen(e) and onEdit(e) simple triggers in a no-authorization mode that presents some additional complications. For more information, see the guide to the add-on authorization lifecycle.
These restrictions do not apply to doGet() or doPost().
Related
[UPDATE]
I had a look at add-ons and I am afraid this won't work. So let me take a step back and describe what I am trying to achieve.
I have a spreadsheet A, with a list of individual events. Each event is a line item in the spreadsheet. The spreadsheet is very long for one, and has many fields that I don't need to expose to event owners (different events different owners). Which means if I allow all these different people edit access to the sheet, it becomes really chaotic.
The solution I came up with is to generate unique IDs programmatically for each event, which I've done. Then for each event, I create an individual form and a pre-filled link, with pre-filled answers that is pulled from the cell values. I intend to give the pre-filled links to event owners when they need to make any updates.
The issue is now I have 100+ forms, and I don't want to have 100+ corresponding tabs set as destinations of these forms. These 100+ forms need to submit responses to one same sheet (tab). Instead I wrote a function for submitted responses to find the right event (the event unique ID is the title of the form) and updates the right cell. This is what you see below processSubmission().
I have tried to write the processSubmission() in the spreadsheet where the events are listed. If I don't set this spreadsheet as destination of these 100+ forms then the spreadsheet doesn't know there is a "submission" event. Therefore the setting the trigger onFormSubmit() in the spreadsheet doesn't work.
Then I moved onFormSubmit() -> processSubmission() and it doesn't set the trigger because as you all pointed out, it's an installable trigger.
What I did manage to to write an onOpen() -> create the onFormSubmission() trigger. That means I had to manually open 100 forms and close them to create that trigger. The triggers are created alright. But turned out for the trigger to actually run I need to manually grant permission!
When I looked at add-on triggers, it says "Add-ons can only create triggers for the file in which the add-on is used. That is, an add-on that is used in Google Doc A cannot create a trigger to monitor when Google Doc B is opened." So I think that also rules out the add-on triggers. So now I am out of ideas.
[ORIGINAL]
I made a custom function for the processing of submission responses. I use the form title as a key, and the response answers are written to the corresponding headers in the row with the right key.
My first try was something like this. But it simply didn't execute when the form was submitted:
function onFormSubmit(e){
var form = FormApp.getActiveForm();
var key = form.getTitle();
var responses = e.response;
var ss= SpreadsheetApp.openById(ss_id);
var sheet = spreadsheet.getSheetByName('Launch list');
var frozenRow = sheet.getFrozenRows();
var lastRow = sheet.getLastRow();
var lastColumn = sheet.getLastColumn();
var headers = sheet.getRange(1, 1, 1, lastColumn).getValues()[0];
var keyCol = headers.indexOf(key_header) + 1;
var header1Col = headers.indexOf(header_1) + 1;
var header2Col = headers.indexOf(header_2) + 1;
var header3Col = headers.indexOf(header_3) + 1;
var keysRange = sheet.getRange(frozenRow+1, keyCol , lastRow - frozenRow, 1);
var allKys = keysRange.getValues();
for (i=0; i<allKys.length; i++){
var keyValue = allKys[i][0];
if (keyValue === key){
var rowNum = l + frozenRow + 1;
break;
}
else {
continue;
}
}
var dataRow = sheet.getRange(rowNum, 1, 1, lastColumn).getValues()[0];
var lookUp = {};
lookUp[item_title_1] = header1Col ;
lookUp[item_title_2] = header2Col ;
lookUp[item_title_3] = header3Col ;
var items = form.getItems();
var cnt = 0;
var changes = [];
for (i=0; i< items.length; i++){
var item = items[i];
var title = item.getTitle();
var itemResponse = responses.getResponseForItem(item);
var existingValue = dataRow[lookUp[title] -1];
if ((itemResponse.getResponse() !=='' || itemResponse.getResponse() !== undefined) && itemResponse.getResponse() != existingValue){
cnt++;
var cell = sheet.getRange(rowNum, lookUp[title], 1, 1);
cell.setValue(itemResponse.getResponse());
changes.push(title);
}
else {
continue;
}
}
Logger.log('Made ',cnt,'changes for launch ',featureID,': ',changes);
}
I also tried a slightly different approach but also didn't work:
function onFormSubmit(){
processSubmission();
}
// Processing form submission
function processSubmission() {
var form = FormApp.getActiveForm();
var key = form.getTitle();
var responses = form.getResponses()[form.getResponses().length-1];
// The rest is the same.
}
Manually running the function in the second approach proved my function processSubmission() works. Manually add a onFormSubmit() trigger via the Apps Script Dashboard is not going to be possible because I am generating hundreds of forms (one for each key) programmatically so I chose to have onFormSubmit(e) in the template and every new form is a copy of the template which should also have copies of these functions. But it just doesn't work! Any insight?
The onFormSubmit trigger is an installable trigger which means that it requires to be set up before being able to use it.
It's also important to keep in mind the following, according to the installable triggers documentation:
Script executions and API requests do not cause triggers to run. For example, calling FormResponse.submit() to submit a new form response does not cause the form's submit trigger to run.
What you can do instead is to create the trigger programmatically, something similar to this:
function createTrigger() {
ScriptApp.newTrigger('onFormSubmit')
.forForm('FORM_KEY')
.onFormSubmit()
.create();
}
Reference
Apps Script Installable Triggers;
Apps Script FormTriggerBuilder Class.
I need a solution to enable all editors to leave their name stamps after they make changes in a sheet. I was able to come up with script code which does work ONLY FOR OWNER.
I tried to authorize the script by running the script manually from editors accounts - the app has the authorization but even though it doesn't work for Editors.
Norbert Wagner: "However authorizing from the script editor did not work for me. But as soon as I added a menu in the document and executed the function from there, I got an authorization request in the document. From then on also the onEdit function works, for all users. I did this as the documents owner though. " -
maybe this is the solution? But how can I run the onEdit from the
document? Simple edit doesn't work, but how about this menu? Is there
a way to execute ALL SCRIPTS AND FORCE AUTHORIZATION from document level?
function onEdit(e) {
// Your sheet params
var sheetName = "Arkusz1";
var dateModifiedColumnIndex = 3;
var dateModifiedColumnLetter = 'C';
var range = e.range; // range just edited
var sheet = range.getSheet();
if (sheet.getName() !== sheetName) {
return;
}
// If the column isn't our modified date column
if (range.getColumn() != dateModifiedColumnIndex) {
var row = range.getRow();
var user = Session.getActiveUser().getEmail();
user = String(user);
user = user.replace('#gmail.com', '');
var dateModifiedRange = sheet.getRange(dateModifiedColumnLetter + row.toString());
dateModifiedRange.setValue(user);
};
};
This function works only when using owner account.
Even after Editor has authorised script manually from script editor - the onEdit user stamp function doesn't trigger.
Below is another function which I used for testing. When I run it manually from editors account IT WORKS - it saves editor's name in A1.
function USERNAME(){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheets()[0]; // this will assign the first sheet
var cell = sheet.getRange('A1');
var d = new Date();
d.setHours(d.getHours() +1);
var user = Session.getActiveUser().getEmail();
user = String(user);
user = user.replace('#gmail.com', '');
cell.setValue(user);
}
In fact you have to use installable trigger.
Rename your function onEdit(e) with another name for example customFunction(e)
Then you setup an installable trigger using this function :
function createTrigger(){
ScriptApp.newTrigger('customFunction')
.forSpreadsheet(SpreadsheetApp.openById('YOU_SHEET_ID'))
.onEdit()
.create();
}
Or you can setup the trigger manually by going to 'Edit >> Current project's triggers >> Click on + button (bottom right)'
By this way trigger will run each time there is an Edit no matter the user.
Stéphane
I'm using Cognito forms to collect information and then using Zapier to pass this information to my google spreadsheet database. I have found that my scripts in google spreadsheets do not trigger the way I would expect.
This one:
function onEdit(event){
var ColCR = 96; // Column Number of "CR"
var changedRange = event.source.getActiveRange();
if (changedRange.getColumn() == ColCR) {
var state = changedRange.getValue();
var adjacent = event.source.getActiveSheet().getRange(changedRange.getRow(),ColCR+1);
var adjacentv = adjacent.getValue();
var timestamp = Utilities.formatDate(new Date(), "GMT-7", "M/dd/yy', 'h:mm a");
switch (adjacentv) {
case "":
adjacent.setValue("("+timestamp+")"+" "+state);
changedRange.clearContent();
break;
default:
adjacent.setValue(adjacentv+"\n"+"("+timestamp+")"+" "+state);
changedRange.clearContent();
break;
}
}
}
Works fine when a user edits the spreadsheet cell directly but not when Zapier updates the cell. It also works on several different types of triggers when the spreadsheet is modified by a user. What is the difference between editing the spreadsheet directly vs having an app like Zapier edit the form? Can I write a script that would see an edit by an app like Zapier?
This one:
function Timestamp() {
var sheet = SpreadsheetApp.getActiveSpreadsheet();
var cell = sheet.getActiveCell();
var comments = cell.getComment();
var formattedDate = Utilities.formatDate(new Date(), "GMT-7", "M/dd/yy', 'h:mm a ");
Newline works in msgBox, but not in Note.
comments = comments + "Mod: " + formattedDate;
Browser.msgBox(comments);
cell.setComment(comments);
}
Works fine when a user edits a cell and is triggered by a On Change trigger and also works when Zapier updates a cell but when Zapier updates more then one cell in a row it only puts the comment in the first cell in the range. How would I modify the script so that it triggers on every change to every cell and not just the range?
Im sure it has something to do with how Zapier is interacting with my spreadsheet but I don't understand how Zapier edits are any different than user edits?
Thanks for any suggestions or recommendations.
I've experienced this issue as well, and in the end came to the conclusion that the Zapier action uses the API to update the Google Sheet, thus not triggering the trigger. Details available here in Google's documentation of Simple Triggers:
https://developers.google.com/apps-script/guides/triggers/
I'm new to Apps Script and have been slowing teaching myself. I have searched Stack Over Flow and found similar questions and tried to work through the issue using the information found but thus far with no success. Hopefully someone here can give a newbie some guidance.
I have a spreadsheet that I am sharing with other users. There are a few ranges with formulas in them that I am trying to protect. I have written a script that can change the forumlas within the protected ranges. It runs perfectly under my account but the shared users are unable to run the script because they "don't have permission".
I have tried to write a bit of code that tries to grant temporary access to edit the protected ranges. My thought was to have three different functions. The first function removes the protections. The second function performs the changes to the now unprotected ranges. The third function then protects the ranges again. The code below works perfectly under my account. However when logged in as a shared user it causes an alert saying that "You do not have permission to perform.."
Can someone please tell me where I am going wrong and point me in the right direction?
function myFunction() {
//add protections back
var ss = SpreadsheetApp.getActive();
var sheet = ss.getSheetByName('Spray Template');
var range = sheet.getRange('B15:E15');
var range2 = sheet.getRange('G15:H15');
var range3 = sheet.getRange('D16:E16');
var ranges = [range,range2,range3];
for (var j = 0; j<3; j++){
ranges[j].protect().removeEditor('email_here#gmail.com');
}
}
function myFunction2(){
//remove protections
var ss = SpreadsheetApp.getActive();
var ss2= ss.getSheetByName('Spray Template');
var protections = ss2.getProtections(SpreadsheetApp.ProtectionType.RANGE);
f
or (var i = 0; i < protections.length; i++) {
var protection = protections[i];
protection.addEditor('email_here#gmail.com');
SpreadsheetApp.flush();
protection.remove();
}
}
function test() {
myFunction2();
functionThatChangesRange();
myFunction();
}
I imagine that without permission to edit/access those protected ranges, other people will also lack permission to edit the protection itself (even through scripts).
This is because the script inherits its permissions from the user that is running it (ie the script runs under that person's account).
Obviously, you can't both protect a range, and also allow anyone to change the protection - that would defeat the whole purpose!
I saw it suggested elsewhere to leave one cell unprotected and setup a script within the document that runs 'onedit'. Whenever the user changes the value of that cell the script should run.
One can use onEdit to run the script or installable trigger. However, I have not identified a way in which a script can be called from a custom menu or a button and edit successfully protected ranges.
Is there a way on to run a script whenever the user switches between sheets in a Google Sheets Spreadsheet?
More or less like onOpen, but instead of running when the document is opened, it should fire every time the user switches to another sheet.
Here is my work-around for onSheetChange:
function saveActiveSheet() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var actsheet = ss.getActiveSheet();
// The onSelectionChange() function executes in a separate thread
// which does not use any script global variables, so use the
// PropertiesService to maintain the user global state.
var userProperties = PropertiesService.getUserProperties();
userProperties.setProperty('ACTIVE_SHEET', actsheet.getSheetName());
}
function onSheetChange(e) {
// Do anything needed after a new sheet/tab selection
}
function onSelectionChange(e) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
// Get current sheet name and compare to previously saved sheet
var currentactsheet = ss.getActiveSheet();
var currentactsheetname = currentactsheet.getSheetName();
var userProperties = PropertiesService.getUserProperties();
var actsheetname = userProperties.getProperty('ACTIVE_SHEET');
if (currentactsheetname !== actsheetname) { // New sheet selected
saveActiveSheet();
onSheetChange(e); // Call custom sheet change trigger
}
// Do anything needed when a different range is selected on the same sheet
else {
var range = e.range;
}
}
function onOpen(e) {
saveActiveSheet();
}
UPDATE: On April 2020 Google added onSelectionChange(e) which could be used to check if the user switched between sheets aka tabs.
At this time there isn't a trigger related to switch for one sheet to another. To learn about the available triggers in Google Apps Script, please checkout Triggers and events - Google Apps Script Guides
As a workaround you could a custom menu to and SpreadsheetApp.setActivesheet to switch between sheets by using a script instead of using tabs and including in that script a call to the function to be run when switching from one sheet to another.
I have created a "Feature request" to implement "onSheetChange" trigger at google issue tracker. You can star it, so google now you want this:
https://issuetracker.google.com/issues/72140210
And here is related question with possible workaround: How to capture change tab event in Google Spreadsheet?
you can use
sheet1.showSheet();
sheet2.hideSheet();