Google Sheets App Script Edit Protected Cell or Object Error - google-apps-script

I have a Google Sheets script that I've been using for the past 2+ years that grabs a chunk of data and transcribes it into a pair of tally sheets, then clears the cells that had been filled creating said chunk of data so the process can be started over. This is used for an inventory calculation system. The script has reliably worked up until roughly 3 weeks ago, but I now encounter an edit protection error when users with limited access to the sheet attempt to run the script. None of the editable cells/ranges reference in the script are locked to any user, but the sheet does have protection on cells I do not want anybody to make inadvertent changes to.
The script is:
function CopyErADD() {
var sss=SpreadsheetApp.getActiveSpreadsheet();
var sheet = sss.getSheetByName('ADDER'); //Entry Sheet
var rangeIn = sheet.getRange('B32:N45'); //Range to copy into "Incoming" sheet
var dataIn = rangeIn.getValues();
var ts = sss.getSheetByName('Incoming'); //Tally sheet in
ts.getRange(ts.getLastRow()+1, 1, dataIn.length, dataIn[0].length).setValues(dataIn);
var rangeOut = sheet.getRange('B48:O54'); //Range to copy into "Outgoing" sheet
var dataOut = rangeOut.getValues();
var tss = sss.getSheetByName('Outgoing'); //Tally sheet out
tss.getRange(tss.getLastRow()+1, 1, dataOut.length, dataOut[0].length).setValues(dataOut);
SpreadsheetApp.flush() // NEWLY ADDED PER METAMAN'S SUGGESTION
sheet.getRange('E2:E5').clearContent();
sheet.getRange('B7:B20').clearContent();
sheet.getRange('E7:H20').clearContent();
sheet.getRange('I7:I20').setValue('kg');
sheet.getRange('L7:L20').clearContent();
sheet.getRange('B24:B29').clearContent();
sheet.getRange('J24:J29').clearContent();
}
I also have an "erase only" script that runs the second second part of the script only (if data has been entered incorrectly) that executes perfectly fine. It is only when coupled with the copy/transcribe portion of the script that the protection error occurs.
function ErADD() {
var sss=SpreadsheetApp.getActiveSpreadsheet();
var sheet = sss.getSheetByName('ADDER');
sheet.getRange('E2:E5').clearContent();
sheet.getRange('B7:B20').clearContent();
sheet.getRange('E7:H20').clearContent();
sheet.getRange('I7:I20').setValue('kg');
sheet.getRange('L7:L20').clearContent();
sheet.getRange('B24:B29').clearContent();
sheet.getRange('J24:J29').clearContent();
}
Commenting out the sheet.getRange.... etc clearContent and setValue portion of the first script allows it to complete successfully, so I attempted to create a master function that calls the CopyErAdd script (sans clear portion) and then calls the ErADD script, and I encounter the same error. Both script can be run on their own successfully, but when combined the erase portion encounters the error.
Does anybody see any issues that I am missing, or did something occur over the past few weeks that I'm not aware of that could cause this protection error?
I appreciate any ideas anybody might have.
Edit - Thank you MetaMan for the tips for making the script more efficient.
As for the protection, "Incoming" and "Outgoing" copy destination sheets are completely open, no protection at all. "ADDER" sheet is protected except certain cells that are edited by user, and are referenced in the script.
E2:E5 (actually E2:I5 open, since the macro button sits on top of the F2:I5 range), B7:B20, E7:I20, L7:L20, B24:B29, J24:J29, All unprotected.
Adding the line
SpreadsheetApp.flush()
in between the copy section and clearing section of the code worked (as shown in the updated code snippet). I'm no longer encountering a range protection error.
I really appreciate the fix, but I'm boggled over why I've never needed that line until recently. I guess you don't need flush() until you do.

You could replace this:
sheet.getRange('I7').setValue('kg');
sheet.getRange('I8').setValue('kg');
sheet.getRange('I9').setValue('kg');
sheet.getRange('I10').setValue('kg');
sheet.getRange('I11').setValue('kg');
sheet.getRange('I12').setValue('kg');
sheet.getRange('I13').setValue('kg');
sheet.getRange('I14').setValue('kg');
sheet.getRange('I15').setValue('kg');
sheet.getRange('I16').setValue('kg');
sheet.getRange('I17').setValue('kg');
sheet.getRange('I18').setValue('kg');
sheet.getRange('I19').setValue('kg');
sheet.getRange('I20').setValue('kg');
with this:
sheet.getRange('I7:I20).setValue('kg');
also replace this:
var ss = sss.getSheetByName('ADDER'); //Entry sheet
var sheet = SpreadsheetApp.getActive().getSheetByName('ADDER');
with this:
var sheet = sss.getSheetByName('ADDER);
If you are doing anything immediate after this function with the data then you might wish to add SpreadsheetApp.flush()
I can't comment on the protection since none of that information was provided.

Related

How to remove google apps script "Exception: Conditional format rule cannot reference a different sheet."

I have several similar spreadsheets I am working with. Each spreadsheet has 3 sheets of the same name: "Plan", "Class", and "Coach". I want the conditional formatting within the sheets of my spreadsheets to be exactly the same. I was helped a little while ago in creating a script that could do so, and it worked perfectly fine in my test spreadsheets (I don't like to put something in my actual spreadsheets until I know it will work properly). The script uses getConditionalFormatRules on all the sheets from my source sheet and then uses setConditionalFormatRules and the other sheet ID's to recreate the source spreadsheet's rules in my other spreadsheets. I have now placed my code into the actual spreadsheet, but I am now experiencing a problem.
Every time I run my script, I get this error:
Exception: Conditional format rule cannot reference a different sheet.
I get this message when the script is copying the conditional formatting from the sheet called "class". The other two sheets' formatting gets copied perfectly fine. I have gone over the code many times now, but I can find no error in my code. I have tried creating new sheets and starting over, and then referencing the recreated sheets, but I still get the same message. I have tried changing the sheet name as well, but to no avail (plus, I would rather not change the sheet name from "Class" because many of my other scripts would also have to change). I am very lost.
This is my current script:
function copyConditional(){
var targetT = SpreadsheetApp.openById("Tuesday_ID").getSheetByName("Plan");
var targetW = SpreadsheetApp.openById("Wednesday_ID").getSheetByName("Plan");
var targetTh = SpreadsheetApp.openById("Thursday_ID").getSheetByName("Plan");
var targetF = SpreadsheetApp.openById("Friday_ID").getSheetByName("Plan");
var targetS = SpreadsheetApp.openById("Saturday_ID").getSheetByName("Plan");
var source = SpreadsheetApp.openById("Monday_ID").getSheetByName("Plan");
var rules = source.getConditionalFormatRules();
targetT.setConditionalFormatRules(rules);
targetW.setConditionalFormatRules(rules);
targetTh.setConditionalFormatRules(rules);
targetF.setConditionalFormatRules(rules);
targetS.setConditionalFormatRules(rules);
var targetT2 = SpreadsheetApp.openById("").getSheetByName("Coach");
var targetW2 = SpreadsheetApp.openById("").getSheetByName("Coach");
var targetTh2 = SpreadsheetApp.openById("").getSheetByName("Coach");
var targetF2 = SpreadsheetApp.openById("").getSheetByName("Coach");
var targetS2 = SpreadsheetApp.openById("").getSheetByName("Coach");
var source2 = SpreadsheetApp.openById("").getSheetByName("Coach");
var rules2 = source2.getConditionalFormatRules();
targetT2.setConditionalFormatRules(rules2);
targetW2.setConditionalFormatRules(rules2);
targetTh2.setConditionalFormatRules(rules2);
targetF2.setConditionalFormatRules(rules2);
targetS2.setConditionalFormatRules(rules2);
var targetT1 = SpreadsheetApp.openById("").getSheetByName("Class");
var targetW1 = SpreadsheetApp.openById("").getSheetByName("Class");
var targetTh1 = SpreadsheetApp.openById("").getSheetByName("Class");
var targetF1 = SpreadsheetApp.openById("").getSheetByName("Class");
var targetS1 = SpreadsheetApp.openById("").getSheetByName("Class");
var source1 = SpreadsheetApp.openById("").getSheetByName("Class");
var rules1 = source.getConditionalFormatRules();
targetT1.setConditionalFormatRules(rules1);
targetW1.setConditionalFormatRules(rules1);
targetTh1.setConditionalFormatRules(rules1);
targetF1.setConditionalFormatRules(rules1);
targetS1.setConditionalFormatRules(rules1);
}
The only thing I changed in this code was taking out my sheet ID's, but I can guarantee I had all the correct ID's in the right spots.
I would like to know what I can do to remove the exemption from my script, or if there is maybe another workaround to allow the script to function properly. Thank you!
Edit
This is an image of part of my sheet. The first two rows have rules that will change the background color and font color of a cell depending on its exact value. There is also one rule for every 6 columns (except for the first) that will change the cell's background color if any information at all is entered.
Edit 2
These are all the rules on sheet "Class" as of the moment. I do add rules pretty frequently, but following the same pattern. I am using my script because it can be very time consuming to have to add the same new rule to all 6 of my sheets individually.
My other sheet "Coach" has all the same "text is exactly" rules, except the range changes from A1:EO2 to A1:BF59. The other sheet "Plan", has all the same "text is exactly" rules, but changes the range to A1:AU59. Sheet "Plan" also has some unique rules, which I will add as well.
EDIT
I never found out why the script did not work on my original sheets. I had to recreate all of my sheets from scratch, rewrite my script in the new spreadsheets, and paste in the new spreadsheet ID's into my script to get my desired effect. At this point, I have determined there must have been some fundamental flaw in the sheet itself, and that my problem had nothing to do with my code.
I am not posting this as an answer, because creating new spreadsheets from scratch did not exactly solve the problem. I consider it a lengthy and annoying workaround more than a functioning solution. Thanks to everyone who tried to help though!

sheet.getLastRow() not working when launched by trigger

A short description of the issue:
When running an automatic script with trigger head timebased the script failed
With the following error description
Exception: The starting row of the range is too small. at fetchData(wkapi2:4:20)
(which means that the sheet is empty) and this is not the case the sheet has 300 rows.
It worked well until 2 days ago....I did not change the script nor any settings
When I run the script manually, then everything works fine...
The line of code is as follows :
function fetchData() {
var options = {'Cache-Control' : 'max-age=0, must-revalidate'}; //I've used this before to try and prevent google from giving me cached data when making API calls..seems to get the job done
var sheet = SpreadsheetApp.getActiveSheet(); //This script is bound to the corresponding sheet so we can use this conveinience method
var cell = sheet.getRange(sheet.getLastRow(),1); //Get a reference to current last cell in Column A
The scripts Fails at the
sheet.getRange (... ) function when automatically run
When I run manually it works
When run without a "active" spreadsheet, (i.e., a spreadsheet that is visible in the browser), "active" sheet would refer to the first sheet. Use Spreadsheet.getSheetByName() instead of getActiveSheet().

Apps Script: how do you copy values from specific columns to a different spreadsheet via a time trigger, button press or sheet update?

I need to copy the values of multiple ranges of data from my main spreadsheet into another spreadsheet and then copy a different range of data back to the main spreadsheet.
Here are my example spreadsheets:
The main spreadsheet (two sheets: 1 - A sheet of pasted values alongside columns of user comments. 2 - A sheet with a button that runs the Apps Script)
The live data spreadsheet (pulls and formats the required data from other sheets).
Previously, I’ve used .copyTo() to copy the values of data and paste them elsewhere on the same spreadsheet. However, this method can’t be used to copy data to a different spreadsheet. Copying data from multiple ranges is also causing me issues. Here is my code:
function RefreshSheetData() {
// 1a. Run the script when a button is pressed (Main Spreadsheet - ‘Update report button!A3’)
// 1b. Run the script at a certain time (every Monday at 10 AM)
// 1c. Run the script when data in a sheet is replaced with new data via a formula – NOT POSSIBLE.
// 2. Copy email address and user comments (Main Spreadsheet - Editable report - J3:J & AM3:AR)
// 3. Clear the “Updated user comments” sheet below the header row (Live data Spreadsheet - Updated user comments - A2:G)
// 4. Paste the values (Live data Spreadsheet - Updated user comments - A2:G)
// 5. Clear the “Editable report” below the header rows (Main Spreadsheet - Editable report – B3:AR)
// 6. Copy the Live sheet (which should now include the most recent user comments via array vlookup) (Live data Spreadsheet - live data – A3:AQ)
// 7. Paste the values (Main Spreadsheet - Editable report - B3:AR)
// 8. Add the (United Kingdom) time and date (Main Spreadsheet - Update report button - A10)
// 9. Add the time and date (Live data Spreadsheet - Updated user comments - J1)
ScriptApp.newTrigger('RefreshSheetData')
.timeBased()
.onWeekDay(ScriptApp.WeekDay.MONDAY)
.atHour(10)
.create();
var ss = SpreadsheetApp.getActiveSpreadsheet();
var startSheet = ss.getSheetByName('Editable report');
var sourceRange = startSheet.getRangeList(['J3:J', 'AM3:AR']);
var sourceValues = sourceRange.getValues();
var target = SpreadsheetApp.openById('1OHQHefYvE4vZZPr8jgziy_L3-UBf1WSoKzMWQ8LUz6w');
var targetSheet = target.getSheetByName('Updated user comments');
var clearTargetRange = targetSheet.getRange('A2:G').clearContent();
var targetRange = targetSheet.getRange('A2').setValues(sourceValues);
var liveTargetSheet = target.getSheetByName('Live data');
var liveSourceRange = liveTargetSheet.getRange('A3:AQ').getValues();
var clearMainRange = startSheet.getRange('B3:AR').clearContent();
var startRange = startSheet.getRange('B3').setValues(liveSourceRange);
SpreadsheetApp.getActive().getRange('A10').setValue(new Date());
targetSheet.getRange('J1').setValue(new Date())
}
Any tips would be appreciated. Thank you.
Part of my question was how to trigger a script when a formula output changes. That’s not possible. Neither .onEdit or .onChange triggers work as they only respond to user actions. They won't run when the value of an IMPORTRANGE or alternative formula changes.
Here some suggestions referring to each of the steps mentioned in your Apps Script file
1b. To run the script at a certain time: please use the installable trigger "Time driven"
1c. To run the script when data in the sheet is updated: please use the onEdit trigger
1d. You can combine all triggers by simply adding as many, as required.
To copy and paste data, you just need the methods getValues() and setValues(), which you were using already, the important thing is that you chose the "to copy" range correctly.
You can clear a range with clear()
See 3.
You can copy a sheet with copyTo(), however keep in mind that if using this method, your data in the copied sheet will automatically be updated if there is a change in the original sheet. If you want the values to remain static, you have to copy and paste them with copyValues() and setValues().
See 2. and 5.
See here how to get and format the date in Apps Script
Assign the date to a variable and use setValue()
I encourage you to try and build the script based on those steps
yourself, the Apps Script documentation provides you good reference
and guidance, how to do so. If you encounter specific problems during
one of the steps which you cannot solve with the documentation, feel
free to ask!

How are multiple onEdit() functions processed?

If I have a sheet with 20 separate onEdit() scripts running, would any problems arise if 2 unique edits are made at approximately the same time by different users? More specifically:
1) What is the time resolution for an edit to be recorded? For instance, the probability of two people editing at the same millisecond is very small, but perhaps within the same second may not be terribly improbable. If two people edit in the same second, would it fail to recognize these as separate edits? Or can it clearly recognize every edit as a unique event regardless of how closely they happened?
2) If one onEdit() script is triggered and does not finish before another edit occurs, would this cause a failure in the second edit to be properly evaluated?
3) I have about 20 scripts running on a sheet with about 20 tabs and about 7 users on it at any given time. Are there any other problems you foresee that would cause some onEdit() scripts to not work?
My script checks to see if the edit was made in column x on sheet x and if so, copies it to the next sheet and deletes the row from the first sheet. It is currently failing about 30% of the time. I don't know why. It hasn't done this in the past.
My code looks like this:
function onEdit(e){
var edited_range = e.range;
var edited_row = edited_range.getRow();
var activeSheet = e.source.getActiveSheet();
var ss = SpreadsheetApp.getActiveSpreadsheet();
var source_sheet = ss.getSheetByName("N.Arb Pitches");
var target_sheet = ss.getSheetByName("N.Arb In Progress");
var last_row = target_sheet.getLastRow();
var range_values = target_sheet.getRange("A1:A").getValues();
var range_length = range_values.filter(String).length + 1;
var target_range = target_sheet.getRange(range_length,1);
if (activeSheet.getName() == "N.Arb Pitches") {
if (edited_range.getColumn() == 13) {
if (edited_range.getValue().toLowerCase() == "yes") {
target_sheet.insertRowAfter(last_row);
source_sheet.getRange(edited_row,1,1,12).copyTo(target_range);
source_sheet.deleteRow(edited_row) }}};
}
There is only 1 onEdit(e) function in each project, and I have this many projects in my sheet. Each project has a function like the one above but with sheet names changed.
Without any code to review, I assume you have multiple onEdit() methods in your app script project.
You probably have a number of *.gs files defined as follows:
\** onEdit() defined in, for example, script1.gs *\
function onEdit(e) {...}
And another
\** onEdit() defined in, for example, script2.gs *\
function onEdit(e) {...}
If that's the case then you have a problem. Each time you define a method with the same name (within the same apps script project), regardless of which script file its in, the last definition overrides the ones that came before. So of your 20 onEdit() functions, only the last one defined will be valid.
EDIT
Assigning multiple apps script projects to a single spreadsheet with their own onEdit() is viable. However, if the sheet is heavily used by multiple parties those onEdit() methods will likely give rise to concurrency problems. You can try using LockService to mitigate those issues.

Copy Worksheet to New Workbook Doesn't Allow onEdit Apps Script to Run Properly

I created a Google spreadsheet template with a worksheet that's called "CollegeTracker" that users can right click on the tab, and "copy to" their workbook. However, my onEdit function from the Apps Script is not working.
function onEdit()
{
var s = SpreadsheetApp.getActiveSheet();
if( s.getName() == "CollegeTracker" )
{ //checks that we're on the correct sheet
var r = s.getActiveCell();
var range = s.getRange(r.getRow(),r.getColumn());
var data = range.getValue();
var schoolRange = s.getRange(r.getRow(),1);
var schoolData = schoolRange.getValue();
var deadlineRange = s.getRange(r.getRow(),3);
var deadlineData = deadlineRange.getValue();
Logger.log(r.getRow());
Logger.log(r.getColumn());
Logger.log(data);
This is the beginning of my code. I added a couple loggers to see what's going on. I typed something in cell A2 but the logger shows row 1 column 1, or A1. I tried in a couple other cells but it always return A1 as the active cell. I repeating this in cell A6 and E7 but the logger always return A1. However, this is reading correctly in the template. I wonder if it's a permissions issue where I have to allow getActiveCell() to work. If this is the case, how would I go about doing this? I copy and pasted the code from my template and pasted into the script editor and it works fine. I can certainly make multiple copies of my script but if I figure this out, I think one copy can work for multiple sheets should be fine. Do you know what's blocking getActiveCell() from working? And if it's the permissions, how do I go about solving this?
Update:
I've been continuing my tests and realized that getActiveSheet() is still thinking that my template sheet is active even though I am working on a new sheet. I wonder if there is a way to determine the sheet I'm working in regardless of the Workbook I'm in.
Thanks!
-Ray