How to modify existing protection of a Google Sheet using App Scripts? - google-apps-script

I have a Google Sheet named Dashboard which is sheet protected manually except A4:C4 cells.
Now what I want is, when "Start 1-Period or Start 2-Period" in cell C6 is selected, the Dashboard will be fully protected that means, it will include protection in cells A4:C4.
Then after 5 minutes, the dashboard protection will go back to its previous stage, that is dashboard is protected except A4:C4 cells.
Please note that there are five editors in the Dashboard Sheet which will remain unchanged. For details of the scenario, please check the attached image.
function onEdit(e){
if (e.range.getA1Notation() === 'C6' && e.range.getValue() === "Start 1-Period") {
refreshSheet();
onePeriod();
}
if (e.range.getA1Notation() === 'C6' && e.range.getValue() === "Start 2-Period") {
refreshSheet();
twoPeriod();
}
}
function refreshSheet() {
//For protecting dashboard while scripts running
var spreadsheet = SpreadsheetApp.getActive();
var dashboard = spreadsheet.getSheetByName("Dashboard");
var protectionms = dashboard.getProtections(SpreadsheetApp.ProtectionType.RANGE);
for (var i = 0; i < protectionms.length; i++) {
var protectionm = protectionms[i];
if (protectionm.canEdit()) {
protectionm.remove();
}
}
var protectionm = dashboard.protect();
var range = dashboard.getRange('A4:C4');
Utilities.sleep(300000);
protectionm.setUnprotectedRanges([range]);
}

Every time you edit cell C6 you apply protection rules to the range A4:C4 according to the selected value. In more detail, every time you edit cell C6 the script will initially remove all current protections. If the selected value of cell C6 is changed to Please Select then no protection is applied. Otherwise, if the new value of C6 is either Start 1-Period or Start 2-Period you apply protection to the range: A4:C4.
Here is the complete solution:
function onEdit(e) {
var sh_name = "Sheet1";
var sh = e.source.getSheetByName(sh_name)
var row = e.range.getRow();
var col = e.range.getColumn();
if ( e.source.getActiveSheet().getName() == sh_name && row==6 && col==3 ){
var protections = sh.getProtections(SpreadsheetApp.ProtectionType.RANGE);
for (var i = 0; i < protections.length; i++) {
var protection = protections[i];
if (protection.canEdit()) {
protection.remove();
}
}
var protection = sh.protect();
var selection = sh.getRange('C6').getValue();
var range = sh.getRange('A4:C4');
if ( selection == 'Please Select' ) {
protection.setUnprotectedRanges([range]);
}
}
}
You just need to copy this code to the script editor, save the changes and then the script will take care of this process.
Note that my script applies to the sheet with name Sheet1. Change that part of the code if you work with a different sheet.
References:
Class Protection
onEdit Trigger

Related

Google Scripts get active sheet is always returning left most sheet

I have a couple functions that are trigger buttons in a google sheet to switch tabs and set the active sheet. What is happening is the active sheet is always the left most tab. Ideally I would like to hide all the tabs that the buttons trigger so there isn't much clutter to the spreadsheet, but since the active sheet is always, in this case Dashboard, the current tab I'm on gets closed and it boots me to Round Scorecard. Very new to programming so any help is much appreciated. Thanks!
function onSelectionChange(e){
hideUnnecessarySheets();
}
function hideUnnecessarySheets() {
var ss = SpreadsheetApp.getActive();
var allsheets = ss.getSheets();
SpreadsheetApp.getUi().alert(SpreadsheetApp.getActiveSheet().getName());
for(var s in allsheets){
var sheet = allsheets[s];
// Stop iteration execution if the condition is meet.
if(
(sheet.getName() == "Dashboard") ||
(sheet.getName() == "Score Card")||
(sheet.getName() == "Round Scoreboard")
) continue;
if (
sheet.getName() == SpreadsheetApp.getActiveSheet().getSheetName()
) continue;
hideSheet(sheet.getName());
}
}
function hideSheet(sheetName) {
SpreadsheetApp.getActive().getSheetByName(sheetName).hideSheet();
}
function goToSheet(sheetName, row, col) {
var sheet = SpreadsheetApp.getActive().getSheetByName(sheetName);
SpreadsheetApp.setActiveSheet(sheet);
var range = sheet.getRange(row, col)
SpreadsheetApp.setActiveRange(range);
}
Try this:
function onSelectionChange(e){
const nS = ["Dashboard","Score Card","Round Scoreboard"];
e.source.getSheets().filter(sh => !~nS.indexOf(sh.getName())).forEach(sh => sh.hideSheet());
}

Protecting Range through Google Apps Script

I am trying to protect a range through google apps script in my google sheet. Whenever the data point in column O is checked i.e. value is yes. I would like that row be protected.
Secondly, I have prior protected columns E:H and M:N, which should stay protected.
There are around 1000 rows, 10-15 new rows that need to be protected daily. By the code I have currently written, it removes the protection and then re-adds it, which takes a lot of time. If I remove the part where it removes the rights then it still re-adds the same protection regardless.
Is there anyway to check if the cell is protected, if protected move onto the next row?
Secondly, I'm unable to provide access to the other emails i.e. "add editors"
function removecompleted(){
var ss = SpreadsheetApp.getActiveSpreadsheet()
var purchased = ss.getSheetByName("Purchased Inventory")
last_row_purchased = purchased.getLastRow()
var emails = [
'user#domain.com'
];
var protections = purchased.getProtections(SpreadsheetApp.ProtectionType.RANGE);
for (var i = 0; i < protections.length; i++) {
var protection = protections[i];
if (protection.canEdit()) {
protection.remove();
}
}
for(var i = 1; i < last_row_purchased; i++) {
if(purchased.getRange(i, 15).getValue() == "Yes")
{
target_range = purchased.getRange(i, 1, 1, 15)
protection = target_range.protect().setDescription('Sample protected range');
var me = Session.getEffectiveUser();
protection.addEditor(me);
protection.removeEditors(protection.getEditors())
protection.addEditors(emails);
protection.removeEditors(protection.getEditors());
if (protection.canDomainEdit()) {
protection.setDomainEdit(false);
}
}
}
var range = purchased.getRange('E:H');
var protection = range.protect().setDescription('Always Protected');
var range = purchased.getRange('M:N');
var protection = range.protect().setDescription('Always Protected');
}
You can do this on edit, but rather than checking to see if the cell is protected (which is not that much faster than just setting the protection anyway) you can just check the row of each protected range and remove the protection if its row is equal to the row of the cell that was just unticked:
function onEdit(e) {
// List of sheet names the function should run on
const sheets = ["Sheet1", "Sheet2", "Sheet3", "etc"]
const sheet = e.source.getActiveSheet()
// if current sheet is not in permitted sheets, return
if (!~sheets.indexOf(sheet.getName())) {
return
}
const row = e.range.getRow()
if (e.range.getColumn() === 15) {
const ss = e.source
if (e.value === "TRUE") {
range = sheet.getRange(`${row}:${row}`)
const me = Session.getEffectiveUser().getEmail()
const protection = range.protect()
protection.getEditors().forEach(user => {
protection.removeEditor(user.getEmail())
})
protection.addEditor(me)
}
else {
const protections = sheet.getProtections(SpreadsheetApp.ProtectionType.RANGE)
protections.forEach(function(p) {
if (p.getRange().getRow() === row) {
if (row === 1 && p.getRange().getColumn() !== 1) return
p.remove()
}
})
}
}
}
Code Rundown:
Get the row number of the edited range
Check if the edited cell was column 15
If the value of the cell is "TRUE", protect the row, and remove all editors aside yourself.
If the value of the cell is "FALSE", then get all the sheet's protections, check the value of their row, and if the row matches the edited cell's row, then remove that protection
NB : The final check also has a condition to check if the column is not equal to 1 so to not remove the protections on E:H or M:N.
References:
Class Protection
Class: Sheet
Class: Range

Is there a way to add/delete editors' access to protected cells dynamically by checkbox values on a Google sheet?

I am new to Google Apps Scripts and am puzzled with the problem of manipulating access of different editors. I have over 10 sheets and 20 editors, and I need to allocate their access rights according to their roles. I am thinking of using checkboxes for adding and deleting their access rights. So far these two are the codes I have picked up from the others. One for showing and hiding the timestamps by checking/unchecking the boxes, whilst another for adding and deleting the editors.
For Timestamps
function runEmailAccess(){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sEditors = ss.getSheetByName('Sheet Name');
var sheet = SpreadsheetApp.openById("Sheet ID");
var nAddEditor = sEditors.getRange('A2').getValue();
if (nAddEditor != 0){
var vAddEditor = sEditors.getRange('A3:A'+nAddEditor).getValues();
sheet.addEditors(vAddEditor);
}
var nRemoveEditor = sEditors.getRange('B2').getValue();
if (nRemoveEditor != 0){
var vRemoveEditor = sEditors.getRange('B3:B'+nRemoveEditor).getValues();
for (j=0;j<vRemoveEditor.length;j++) {
sheet.removeEditor(vRemoveEditor[j][0])
}
}
}
For checkboxes
function onEdit(e){
if (e.range.columnStart == 6 && e.range.columnEnd == 6 && e.range.rowStart <= 20) {
var ckeckboxRange = "F1:F20";
var date = new Date();
var range = e.source.getRange(ckeckboxRange);
var values = range.getValues().map(function(e) {return e[0] === true ? [date] : [""]});
range.offset(0, 1).setValues(values);
}
}
var SpreadSheet = SpreadsheetApp.getActiveSpreadsheet();
var editors = SpreadSheet.getEditors();
for (var i = 0; i < editors.length; i++) {
SpreadSheet.removeEditor(editors[i]);
};
And finally we have the expected outcome as it would be a clearer picture of what I have described. It is like a command portal to control different access rights on a holistic level.
Expected outcome
Many thanks to all useful comments.
You need the the following components:
onEdit trigger
Checking which checkbox has been edited and making the connection to the respective sheet
Retrieve the editor from the line wiht the checkbox
Verify either the box has been checked or unechecked
Below is a sample providing this funcitonality:
function onEdit(e){
var ss = SpreadsheetApp.getActive();
//change name of the sheet if necessary!
var sheetWithCheckBoxes = ss.getSheetByName("Sheet1");
var column = e.range.getColumn();
if (e.range.getSheet().getName() == sheetWithCheckBoxes.getName() && (column == 3 || column == 4 || column == 5)) {
Logger.log("if");
var spreadsheet2 = SpreadsheetApp.openById("XXX");
var sheet;
switch (column){
case 3:
sheet = ss.getSheetByName("name");
break;
case 4:
sheet = ss.getSheetByName("name2");
break;
case 5:
sheet = spreadsheet2.getSheetByName("name3");
break;
}
var protection = sheet.protect();
if (protection.canDomainEdit()) {
protection.setDomainEdit(false);
}
var editor = sheetWithCheckBoxes.getRange(e.range.getRow(), 2).getValue();
Logger.log(e.value);
if(e.value == "TRUE"){
Logger.log("true");
protection.addEditor(editor);
}
if(e.value == "FALSE"){
Logger.log("false");
protection.removeEditor(editor);
}
}
}
Note:
In this sample the first two sheets are in the same spreadsheet like the sheet with the checkboxes and the wthird dsheet is in a different spreadsheet.
Please adapt the sheets and spreadsheets accoring to you needs.
UPDATE
For setting not only the sheet protection, but also share the spreadsheet with new users, you need to use the method DriveApp.getFileById(id).addEditor(editor).
However, DriveApp.getFileById(id) is a call that cannot be triggered with a simple onEdit trigger due to restrictions.
Solution:
Bind an installable onEdit trigger to your function instead of the simple one. Make sure to rename the function beforehand to avoid conflicts because of the simple and installable trigger being fired simultaneously.
Sample setting up both the spreadsheet and sheet permissions:
function Edit(e){
var ss = SpreadsheetApp.getActive();
//change name of the sheet if necessary!
var sheetWithCheckBoxes = ss.getSheetByName("Sheet1");
var column = e.range.getColumn();
if (e.range.getSheet().getName() == sheetWithCheckBoxes.getName() && (column == 3 || column == 4 || column == 5)) {
var spreadsheet2 = SpreadsheetApp.openById("XXX");
var sheet;
var id;
switch (column){
case 3:
sheet = ss.getSheetByName("name");
id = ss.getId();
break;
case 4:
sheet = ss.getSheetByName("name2");
id = ss.getId();
break;
case 5:
id = spreadsheet2.getId();
sheet = spreadsheet2.getSheetByName("name3");
break;
}
var protection = sheet.protect();
if (protection.canDomainEdit()) {
protection.setDomainEdit(false);
}
var editor = sheetWithCheckBoxes.getRange(e.range.getRow(), 2).getValue();
Logger.log(e.value);
if(e.value == "TRUE"){
DriveApp.getFileById(id).addEditor(editor);
protection.addEditor(editor);
}
if(e.value == "FALSE"){
protection.removeEditor(editor);
}
}
}

Google Sheets - Locking Cells after value reached and one other thing

Making a self-scheduling spreadsheet for my co-workers. The goal is for them to put their hours (12 or 6) in the cell that corresponds with the date and their name. I am trying to have it so that a date column will lockout after the sum cell for that column reaches a certain total.
I am using this (Google Script Lock Cells) as my basis but I am not having much luck.
function myFunction() {
function onOpen() {
var ss = SpreadsheetApp.getActive();
var source = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Sheet1");
var cell = source.getRange("B12").getValue();
var range = ss.getRange("B2:B11");
if (cell == 10) {
// Protect range B2:B11 if cell 'B12' = 10
var protection = range.protect().setDescription('Sample protected range');
Logger.log
} else {
// Remove protection if cell 'B12' is anything other than 10
var protections = ss.getProtections(SpreadsheetApp.ProtectionType.RANGE);
for (var i = 0; i < protections.length; i++) {
var protection = protections[i];
protection.remove();
}
}
}
}
I am also looking to have the sum represent shits rather than hours so if a person wrote "12" the sum column would record "1"
Any help would be amazing.
You should change several things
Do not nest onOpen inside myFunction - it will not work
If you want the protections to be update on every update of B12 - you should use the onEdit trigger instead of onOpen
It is better to replace cell == 10 through cell >= 10 to account for the possibility that the value accidentally trespasses the maximum
4.It is not enough to create a protection, to make it useful you need to remove editors from it
Only the owner of the script (you) should maintain access
For this, use an installable trigger instead of a simple one, to make sure that it runs always as you and not the user that opens the document
Sample (to be bound to an installable onEdit trigger):
function bindAnOnEditTiggerToMe() {
var ss = SpreadsheetApp.getActive();
var source = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Sheet1");
var cell = source.getRange("B12").getValue();
var range = ss.getRange("B2:B11");
if (cell >= 10) {
// Protect range B2:B11 if cell 'B12' = 10
var protection = range.protect().setDescription('Sample protected range');
var me = Session.getEffectiveUser();
protection.addEditor(me);
protection.removeEditors(protection.getEditors());
if (protection.canDomainEdit()) {
protection.setDomainEdit(false);
}
} else {
// Remove protection if cell 'B12' is anything other than 10
var protections = ss.getProtections(SpreadsheetApp.ProtectionType.RANGE);
for (var i = 0; i < protections.length; i++) {
var protection = protections[i];
protection.remove();
}
}
}

protect/unprotect range if a specific cell has changed to a specific value

i want to make a specific range not changeable. but if a specific cell has changed from "no" to "yes" then the range should be changeable. and if the specific cell changed from "yes" to "no" then the range should be not changeable again.
How could I realize that in googe app script?
Thanks for help.
Something like below should get you started. Basically, protecting stuff in Apps Script is easy; Removing the protection is more tedious. Because of that, I'd recommend adding a description to the protected range in question (as I've done with "Test1"), after which, the below loop would find and remove it with ease.
I've attached it to this demo spreadsheet so you have context with the data I was working with.
function onEdit(event){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sh = ss.getSheets()[0]; // 0 == first sheet.
var ee = event.source.getActiveRange().getA1Notation();
if (ee == "A2") { // Check if edited cell is the one we're watching.
if (event.value == "yes"){ // If the value == "yes", do stuff.
var protections = sh.getProtections(SpreadsheetApp.ProtectionType.RANGE);
for (var i = 0; i < protections.length; i++) {
if (protections[i].getDescription() == 'Test1') { // If protection description == "Test1"...
protections[i].remove(); // then remove protection.
}
}
}
}
}
function setProtected(){
// Sets the desired range to protected with "Test1" description for demonstration.
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sh = ss.getSheets()[0];
var protectedRows = sh.getRange("C2:C11");
protectedRows.protect().setDescription("Test1");
}