Protect row when drop down menu changes to "Locked" using app scripts - google-apps-script

I am trying to simply protect an entire row (add editors) when someone changes the drop-down menu to "Locked", even the drop-down menu should be protected that is on the same row. This is something that I want to implement in almost all rows of the sheet.
I have found multiple resources that help but I can't combine it properly, would be very thankful for any help!
Bellow are some helpful links I found.
Google sheets: protect cells conditionally?
Protecting Cells Based on Contents of Other Cells in Google Sheets
Protect ranges with google apps script

Add this code to your spreadsheet's script editor and set an onEdit trigger for the below mentioned function.
Replace the email#gmail.com string in code with your actual email
NOTE : This script assumes that first row of the sheet may have three option depending on which the protection of that range is controlled.
Lock : locks that particular column
Unlock : unlocks that particular column
blank : does nothing
function onEdit(e){
var x = [e.range.columnStart,e.range.rowStart,e.range.columnEnd,e.range.rowEnd]
if (x[0]==x[2] && x[1]==1 && x[3]==1){
var lockRangeStart = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet().getRange(1,x[0]).getA1Notation();
var lastCol = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet().getLastRow();
var lockRangeName = (lockRangeStart+":"+lockRangeStart.match(/(\D*)/)[1]+lastCol);
var lockRange = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet().getRange(lockRangeName);
if(e.value == "Lock"){
var protection = lockRange.protect().setDescription('Locked Range');
protection.addEditor("email#gmail.com");
protection.removeEditors(protection.getEditors());
if (protection.canDomainEdit()) {
protection.setDomainEdit(false);
}
}
else if(e.value == "Unlock") {
var ss = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var protections = ss.getProtections(SpreadsheetApp.ProtectionType.RANGE);
for (var i = 0; i < protections.length; i++) {
var protection = protections[i];
if (protection.canEdit() && protection.getRange().getA1Notation() == lockRangeName ) {
protection.remove();
}
}
}
}
}

Related

How to unprotect a cell on a protected sheet using Google Apps Scripts? [duplicate]

I have a spreadsheet that I owner and I have 10 users with editor permission
as there are quite a lot of cells to unlock or lock, it might be best to lock everything first
and unlock the ones that they can safely use without accidentally editing a formula incorrectly
now I'm just guessing what the best solution would be
I would like to avoid that users is deleted
if it matters then no one is in a group
these would be the ones that the editors can edit
['B3:U27', 'W3:AP27', 'E29:E31', 'I29:I31', 'M29:M31', 'Q29:Q31', 'U29:U31', 'Z29:Z31', 'AD29:AD31', 'AH29:AH31', 'AL29:AL31', 'AP29:AP31', 'B29', 'F29', 'J29', 'N29', 'R29', 'W29', 'AA29', 'AE29', 'AI29', 'AM29', 'C29', 'G29', 'K29', 'O29', 'S29', 'X29', 'AB29', 'AF29', 'AJ29', 'AN29', 'D29', 'H29', 'L29', 'P29', 'T29', 'Y29', 'AC29', 'AG29', 'AK29', 'AO29', 'B31', 'F31', 'J31', 'N31', 'R31', 'W31', 'AA31', 'AE31', 'AI31', 'AM31', 'C31', 'G31', 'K31', 'O31', 'S31', 'X31', 'AB31', 'AF31', 'AJ31', 'AN31', 'D31', 'H31', 'L31', 'P31', 'T31', 'Y31', 'AC31', 'AG31', 'AK31', 'AO31', 'B33:C33', 'F33:G33', 'J33:K33', 'N33:O33', 'R33:S33', 'W33:X33', 'AA33:AB33', 'AE33:AF33', 'AI33:AJ33', 'AM33:AN33' ,'D33:E33', 'H33:I33', 'L33:M33', 'P33:Q33', 'T33:U33', 'Y33:Z33', 'AC33:AD33', 'AG33:AH33', 'AK33:AL33', 'AO33:AP33'];
everything else can be locked
will 1 or 2 extra sheets be created each day and will this apply to all sheets from now on?
does it matter that already have a few sheets with manually protection (without a script)?
users will be notified of this
or is it all just happening in the background?
UPDATE:
I found the right script to protect the page and unlock the range
and it works perfectly
link
function testProtect() {
var sheet = SpreadsheetApp.getActiveSheet();
var protection = sheet.protect().setDescription('Sample protected sheet');
var unprotected = sheet.getRange('B3:I27');
protection.setUnprotectedRanges([unprotected]);
var me = Session.getEffectiveUser();
protection.addEditor(me);
protection.removeEditors(protection.getEditors());
if (protection.canDomainEdit()) {
protection.setDomainEdit(false);
}
}
but there were two things I couldn't figure out
how can I apply it to all sheets and add more cells and ranges?
because if I add another range it gives an error
var unprotected = sheet.getRange('B3:I27','F29:I29');
Exception: B3:I27 cannot be converted to int type (line 4 in the "Code" file)
thanks in advance for any help!
If I understand your post correctly, here's your goal:
Create a script to lock your sheet and only allow specific ranges to be editable for the users with edit access.
Apply that script to all of your sheets on your spreadsheet file.
Recommended Solution:
You can refer to this sample script below where it locks your sheet and only unlock specific ranges you setup.
Sample script
[UPDATED]
function main(){ //Main function to run
var sheets = SpreadsheetApp.getActiveSpreadsheet().getSheets();
var disregard = ["Sheet3","Sheet4","Sheet5"]; //ADD SHEET NAMES HERE THAT YOU WANT TO BE DISREGARDED
for(var x=0; x<sheets.length; x++){
if(disregard.some(data => sheets[x].getName().includes(data))){
//E.g. Disregard any sheet names added on the "disregard" array
}else{
unlockCertainRanges(sheets[x]);
}
}
}
function unlockCertainRanges(currentSheet){ //Function to unlock certain ranges on your spreadshseet
var sheet = currentSheet;
// Remove all range protections in the spreadsheet
var protections = sheet.getProtections(SpreadsheetApp.ProtectionType.RANGE);
for (var i = 0; i < protections.length; i++) {
var protection = protections[i];
protection.remove();
}
var protection = sheet.protect();
//restrict editors to owner
protection.getRange().getA1Notation();
var eds = protection.getEditors();
protection.removeEditors(eds);
//set unprotected ranges
var ranges = protection.getUnprotectedRanges();
var data = ["A1:A5","B6:B10","C11:C15"]; // ADD YOUR RANGES HERE
data.forEach(res => { //LOOPS INTO EVERY ARRAY CONTAINING SPECIFIC RANGES
ranges.push(sheet.getRange(res));
protection.setUnprotectedRanges(ranges); //REMOVES THE PROTECTION ON THE RANGE
});
}
Note:
Borrowed a snippet of script to unlock specific ranges from How to protect a sheet then unprotect specific cells as reference.
Result:
Sample Sheet
All cells are locked except the ranges "A1:A5","B6:B10" & "C11:C15" (contains the "Unlock" word for visibility)
Other cells are locked
Unlocked range cells are editable

Protect spreadsheet then unprotect specific cells and ranges with script

I have a spreadsheet that I owner and I have 10 users with editor permission
as there are quite a lot of cells to unlock or lock, it might be best to lock everything first
and unlock the ones that they can safely use without accidentally editing a formula incorrectly
now I'm just guessing what the best solution would be
I would like to avoid that users is deleted
if it matters then no one is in a group
these would be the ones that the editors can edit
['B3:U27', 'W3:AP27', 'E29:E31', 'I29:I31', 'M29:M31', 'Q29:Q31', 'U29:U31', 'Z29:Z31', 'AD29:AD31', 'AH29:AH31', 'AL29:AL31', 'AP29:AP31', 'B29', 'F29', 'J29', 'N29', 'R29', 'W29', 'AA29', 'AE29', 'AI29', 'AM29', 'C29', 'G29', 'K29', 'O29', 'S29', 'X29', 'AB29', 'AF29', 'AJ29', 'AN29', 'D29', 'H29', 'L29', 'P29', 'T29', 'Y29', 'AC29', 'AG29', 'AK29', 'AO29', 'B31', 'F31', 'J31', 'N31', 'R31', 'W31', 'AA31', 'AE31', 'AI31', 'AM31', 'C31', 'G31', 'K31', 'O31', 'S31', 'X31', 'AB31', 'AF31', 'AJ31', 'AN31', 'D31', 'H31', 'L31', 'P31', 'T31', 'Y31', 'AC31', 'AG31', 'AK31', 'AO31', 'B33:C33', 'F33:G33', 'J33:K33', 'N33:O33', 'R33:S33', 'W33:X33', 'AA33:AB33', 'AE33:AF33', 'AI33:AJ33', 'AM33:AN33' ,'D33:E33', 'H33:I33', 'L33:M33', 'P33:Q33', 'T33:U33', 'Y33:Z33', 'AC33:AD33', 'AG33:AH33', 'AK33:AL33', 'AO33:AP33'];
everything else can be locked
will 1 or 2 extra sheets be created each day and will this apply to all sheets from now on?
does it matter that already have a few sheets with manually protection (without a script)?
users will be notified of this
or is it all just happening in the background?
UPDATE:
I found the right script to protect the page and unlock the range
and it works perfectly
link
function testProtect() {
var sheet = SpreadsheetApp.getActiveSheet();
var protection = sheet.protect().setDescription('Sample protected sheet');
var unprotected = sheet.getRange('B3:I27');
protection.setUnprotectedRanges([unprotected]);
var me = Session.getEffectiveUser();
protection.addEditor(me);
protection.removeEditors(protection.getEditors());
if (protection.canDomainEdit()) {
protection.setDomainEdit(false);
}
}
but there were two things I couldn't figure out
how can I apply it to all sheets and add more cells and ranges?
because if I add another range it gives an error
var unprotected = sheet.getRange('B3:I27','F29:I29');
Exception: B3:I27 cannot be converted to int type (line 4 in the "Code" file)
thanks in advance for any help!
If I understand your post correctly, here's your goal:
Create a script to lock your sheet and only allow specific ranges to be editable for the users with edit access.
Apply that script to all of your sheets on your spreadsheet file.
Recommended Solution:
You can refer to this sample script below where it locks your sheet and only unlock specific ranges you setup.
Sample script
[UPDATED]
function main(){ //Main function to run
var sheets = SpreadsheetApp.getActiveSpreadsheet().getSheets();
var disregard = ["Sheet3","Sheet4","Sheet5"]; //ADD SHEET NAMES HERE THAT YOU WANT TO BE DISREGARDED
for(var x=0; x<sheets.length; x++){
if(disregard.some(data => sheets[x].getName().includes(data))){
//E.g. Disregard any sheet names added on the "disregard" array
}else{
unlockCertainRanges(sheets[x]);
}
}
}
function unlockCertainRanges(currentSheet){ //Function to unlock certain ranges on your spreadshseet
var sheet = currentSheet;
// Remove all range protections in the spreadsheet
var protections = sheet.getProtections(SpreadsheetApp.ProtectionType.RANGE);
for (var i = 0; i < protections.length; i++) {
var protection = protections[i];
protection.remove();
}
var protection = sheet.protect();
//restrict editors to owner
protection.getRange().getA1Notation();
var eds = protection.getEditors();
protection.removeEditors(eds);
//set unprotected ranges
var ranges = protection.getUnprotectedRanges();
var data = ["A1:A5","B6:B10","C11:C15"]; // ADD YOUR RANGES HERE
data.forEach(res => { //LOOPS INTO EVERY ARRAY CONTAINING SPECIFIC RANGES
ranges.push(sheet.getRange(res));
protection.setUnprotectedRanges(ranges); //REMOVES THE PROTECTION ON THE RANGE
});
}
Note:
Borrowed a snippet of script to unlock specific ranges from How to protect a sheet then unprotect specific cells as reference.
Result:
Sample Sheet
All cells are locked except the ranges "A1:A5","B6:B10" & "C11:C15" (contains the "Unlock" word for visibility)
Other cells are locked
Unlocked range cells are editable

Using a script to unprotect a range based on the value of another cell

I'm trying to build a schedule for employees to add appointments to a rep calendar. The goal is once a day has 5 appointments, it gets blacked out, and locked down. I had this working with data validation, but then we decided to add employee names to a dropdown housed in each cell. Since each cell can only have one data validation rule (as far as I can tell) I'm having to use a script to protect/unprotect the cells. I've almost got everything working, but my function seems to only protect the given range. When I added the else clause to unprotect the range given <5 appointments, it runs without failure, but does't actually change anything. (I suspect it protects, then immediately unprotects the range.) Currently, the function only works on Monday (B5:B27).
The code I'm using is:
function onEdit() {
var ss = SpreadsheetApp.getActive();
var maxAppointments = ss.getRange('G3')
if (countMon >= maxAppointments) {
var countMon = ss.getRange('B3').getValue();
var mon = ss.getRange('B5:B27');
var protectMon = mon.protect().setDescription('Protect Monday').setRangeName('monday');
protectMon.removeEditors(protectMon.getEditors());
protectMon.addEditors(['mgr1#domain.com', 'mgr2#domain.com', 'mgr3#domain.com']);
if (protectMon.canDomainEdit()) {
protectMon.setDomainEdit(false);
}
}
else {
var monProtections = ss.getActiveSheet().getProtections(SpreadsheetApp.ProtectionType.RANGE);
for (var i = 0; i < monProtections.length; i++) {
if ( monProtections[i].getRangeName() == 'monday') {
monProtections[i].remove();
}
}
}
};
I also created a simplified example sheet with personal info scrubbed.
You can use this sample code: (this should work for columns Monday-Friday)
function onEdit(e) {
var ss = e.source; // get spreadsheet
var sheet = ss.getActiveSheet();
var cell = e.range; // get edited range
if(sheet.getName() != "Rep1"){
return;
}
var maxAppointments = sheet.getRange('G3').getValue();
var row = cell.getRow();
var col = cell.getColumn();
//Check if edited cell is within B5:F27
if(row>=5 && row<=27 && col>=2 && col<=6){
//Get current date user count
var count = sheet.getRange(3,col).getValue();
if(count>=maxAppointments){
//Select Row 5 to Row 27
var range = sheet.getRange(5,col,23);
// Protect range
var protection = range.protect();
var name = 'Column'+col;
protection.setDescription(name);
Logger.log(protection.getEditors());
protection.removeEditors(protection.getEditors());
var me = Session.getEffectiveUser();
protection.addEditor(me);
Logger.log(protection.getEditors());
if (protection.canDomainEdit()) {
protection.setDomainEdit(false);
}
}else{
var protections = sheet.getProtections(SpreadsheetApp.ProtectionType.RANGE);
Logger.log("Length"+protections.length);
for (var i = 0; i < protections.length; i++) {
var protection = protections[i];
Logger.log(protection.getDescription());
if (protection.getDescription() == 'Column'+col) {
Logger.log("remove");
protection.remove();
}
}
}
}
}
I added some guarding in your current onEdit(), such as modified cell should be in sheet "Rep1", modified cells should be within B5:F27 (done by checking row and column index)
What it does?
Check if modified cell is in Sheet Rep1 and within B5:F27 by getting its sheet name using Sheet.getName(), Row and Column Index using Range.getRow()/Range.getColumn()
If current user count on a specific day is >= the max allowed user count, protect the specific column from row5 to row 27 using Sheet.getRange(row, column, numRows) and Range.protect(). Set the description to "Column"+column index
If current user count a specific day is < the max allowed user count, remove the protection if there is any. Protection to be removed will be based on the protection description set in step 2
Sample Output:
-> This is the view from a non-editor user after protection. Notice that column B and D are not editable (Data validation drop-down list was hidded)
Note:
In the sample code, I just set my primary user as the editor after removing other editors. You can just apply your configuration in your original code where there are multiple editors.
I also did not modify your conditional formatting which sets the cell background to black, maybe you could revisit that since it always change the cell background of columns B-D even though not all columns were locked. See the sample output where columns B and D are locked but not column C

Undo ability not working after adding Script

I have a script to hide or show Rows with a specific value, it is working well, only I have problem that if I do change the value of any cell in the Spreadsheet even in other sheet like (sheet2) and I want to Undo that change, the Undo ability not working till I repeat it more than 30 times!!! And If I delete the script, it works normally.
Do I have to add, change or delete any code of this script to make Undo Ability working normally as before adding script?
Thank you
I tried to changed this line: var ss = SpreadsheetApp.getActiveSpreadsheet();
to let Undo working normally
or to make it atleast be affecting in specific sheet but I could't.
Here is the full script...
function myShowHide() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("sheet1");
var lastRow = sheet.getLastRow();
for (i = 4; i <= lastRow; i++) {
var status = sheet.getRange("J" + i).getValues();
if (status == "X") {
sheet.showRows(i);
}
}
var sheet = ss.getSheetByName("sheet1");
var lastRow = sheet.getLastRow();
for (i = 4; i <= lastRow; i++) {
var status = sheet.getRange("J" + i).getValues();
if (status != "X") {
sheet.hideRows(i);
}
}
}
Assuming that you are running this with an installable onEdit() trigger then this will solve the problem of interacting with other sheets.
function myShowHide(e) {
var sh=e.range.getSheet();
var rg=sh.getRange(4,10,sh.getLastRow(),1);
var sA=rg.getValues();
for(var i=0;i<sA.length;i++) {
if(sA[i][0]=="X") {
sh.showRows(i+4);
}else{
sh.hideRows(i+4);
}
}
}
I setup an installable onEdit trigger for this function.
function myNewShowHide(e) {
var sh=e.range.getSheet();
if(sh.getName()!="Sheet1")return;
myShowHide(e);
}
But solving the problem on Sheet1 depends upon how you want the sheet to run. If you run it with an edit trigger then anytime your editing anywhere on the sheet it runs. You could limit the edit range but that requires knowing what you want to do with the sheet and I don't know that.
My recommendation is not to run this kind of function with an onEdit() trigger. This one will run much faster because I get all of the data at one time but the rows are still shown or hidden one at a time.

How to allow shared users in google sheets to run script that accesses protected sheet [duplicate]

This question already has an answer here:
How to allow onEdit function to affect protected cell in a Google Sheet?
(1 answer)
Closed 11 months ago.
I have a google spreadsheet in which I plan to share with over 50 users who will each have their own sheet. For security measures, I have some script that I would like to run which would allow a user to enter data into their sheet but prevent them from deleting that entry after. The code works fine on my end, but when I tried to test it out by sharing it to one of the users, the script either didn't run or is not allowed to run.
I have done my research on this matter for a while now and cannot seem to apply any of the solutions that I have seen posted on this forum and many others. I am using an onEdit() function which, to the best of my knowledge, is a simple trigger so it shouldn't cause this kind of error. The code is as follows:
function onEdit(event) {
var masterSheetName = "Blank" // sheet where the cells are protected from updates
var helperSheetName = "Blank Copy" // sheet where the values are copied for later checking
// range where edits are "write once": D18:Y157, i.e., rows 18-157 and columns 4-25
var firstDataRow = 18; // only take into account edits on or below this row
var lastDataRow = 157; // only take into account edits on or above this row
var firstDataColumn = 4; // only take into account edits on or to the right of this column
var lastDataColumn = 25; // only take into account edits on or to the left of this column
var miscFirstDataColumnOne = 15; // only take into account edits on or to the right of this column
var miscLastDataColumnOne = 15; // only take into account edits on or to the left of this column
var miscFirstDataColumnTwo = 25; // only take into account edits on or to the right of this column
var miscLastDataColumnTwo = 25; // only take into account edits on or to the right of this column
var miscFirstDataRowTwo = 18;
var miscLastDataRowTwo = 157;
var ss = SpreadsheetApp.getActiveSpreadsheet();
var masterSheet = ss.getActiveSheet();
var masterSheetMiscOne = ss.getActiveSheet();
if (masterSheet.getName() != masterSheetName) return;
if (masterSheetMiscOne.getName() != masterSheetName) return;
var masterCell = masterSheet.getActiveCell();
var masterCellMiscOne = masterSheetMiscOne.getActiveCell();
if (masterCell.getRow() < firstDataRow || masterCell.getColumn() < firstDataColumn ||
masterCell.getRow() > lastDataRow || masterCell.getColumn() > lastDataColumn) return;
var helperSheet = ss.getSheetByName(helperSheetName);
var helperCell = helperSheet.getRange(masterCell.getA1Notation());
var newValue = masterCell.getValue();
var oldValue = helperCell.getValue();
var user = SpreadsheetApp.getActive().getEditors()[1];
var permission = helperSheet.getSheetProtection();
permission.addUser(user);
helperSheet.setSheetProtection(permission);
SpreadsheetApp.flush();
if (oldValue == "") {
helperCell.setValue(newValue);
} else {
masterCell.setValue(oldValue);
Browser.msgBox('You can not delete this value');
}
if ((masterCellMiscOne.getRow() < firstDataRow || masterCellMiscOne.getColumn() < miscFirstDataColumnOne || masterCellMiscOne.getRow() > lastDataRow ||
masterCellMiscOne.getColumn() > miscLastDataColumnOne) & (masterCellMiscOne.getRow() < firstDataRow || masterCellMiscOne.getColumn() < miscLastDataColumnOne ||
masterCellMiscOne.getRow() > lastDataRow || masterCellMiscOne.getColumn() > miscLastDataColumnTwo)) return;
var miscCellValueOne = masterCellMiscOne.getValue();
if (miscCellValueOne !== "") {
Browser.msgBox('Submission Needs To Be Authorized Before Being Added.');
}
SpreadsheetApp.flush();
permission.removeUser(user)
helperSheet.setSheetProtection(permission)
}
}
Each user has a sheet which will also have a copy, (in this code the users sheet is "Blank" and the copy is "Blank Copy"). The blank copy will be protected so they can not edit this because it will allow them to delete the data in their sheet (Blank). This code does work, but I just need for it to work when I share the spreadsheet.
All help is greatly appreciated and here is a link to a copy of the spreadsheet.
https://docs.google.com/spreadsheet/ccc?key=0AhBLjhwt88kUdFZ2cG9CVFNEQy1zVHdJYlp6ZEx5Unc&usp=sharing
Short answer: Create a simple add-on with minimal code refactoring
Explanation
... but when I tried to test it out by sharing it to one of the users, the script either didn't run or is not allowed to run.
Unfortunately this would be the case as the "trigger" that you're currently using (i.e. the onEdit one) -
... cannot access services that require authorization.
Please refer to Simple Triggers > Restrictions.
Alternate solution
You can introduce an onInstall function and publish this code as an add-on with visibility set to Private if folks from only your domain are going to use the sheet/script.
Hope this helps.