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

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();
}
}
}

Related

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

How to ignore already protected sheets with script?

Every day create 2-3 sheets but a minimum of one
these ranges are protecting ["B3:U27", "W3:AP27", "B29:U33", "W29:AP33"]
I reduced the 42 ranges to these 4 ranges to make it faster but still
in 1 minute it can protect about 8 files the problem is that in a few months it can grow more then 100 files
which would take me up to the 6 minute timeout limit and that would interrupt the script.
This is the script I am currently using.
I wonder if it could be modified in some way to ignore the already protected sheets?
function main(){ //Main function to run
var sheets = SpreadsheetApp.getActiveSpreadsheet().getSheets();
var disregard = ["List", "Data", "Template"]; //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 spreadsheet
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 = ["B3:U27", "W3:AP27", "B29:U33", "W29:AP33"]; // 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
});
}
Can it be something that has already been protected or have a padlock on them not to be touched?
I tried to find a way to retrieve the names of the already protected sheets.
I mean something like getSheetName() but for the protected ones.
Or maybe put it in the exceptions if there is already such protection on this description?
setDescription('Already protected');
I don't have much experience in coding; I found a very similar question but I didn't understand much of the code
Does anyone have an idea?
I believe what #MetaMan simply means is that, you need to check first if the sheet does contain a protected range. See code below.
Code:
function main() {
var sheets = SpreadsheetApp.getActiveSpreadsheet().getSheets();
// Get list of sheets protected
var protections = SpreadsheetApp.getActiveSpreadsheet().getProtections(SpreadsheetApp.ProtectionType.SHEET);
var protectedSheets;
// If protections isn't set, initialize as empty array
if (protections)
protectedSheets = protections.map(protection => protection.getDescription());
else
protectedSheets = [];
var disregard = ["List", "Data", "Template"]; //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 {
// If protectedSheets doesn't include the name, process the sheet
if (!protectedSheets.includes(sheets[x].getName()))
unlockCertainRanges(sheets[x]);
}
}
}
function unlockCertainRanges(currentSheet) {
Logger.log("\"" + currentSheet.getName() + "\" is being processed");
var sheet = currentSheet;
var protections = sheet.getProtections(SpreadsheetApp.ProtectionType.RANGE);
for (var i = 0; i < protections.length; i++) {
var protection = protections[i];
protection.remove();
}
// set names of sheets as description for future checks
var protection = sheet.protect().setDescription(currentSheet.getName());
//restrict editors to owner
protection.getRange().getA1Notation();
var eds = protection.getEditors();
protection.removeEditors(eds);
//set unprotected ranges
var ranges = protection.getUnprotectedRanges();
var data = ["B3:U27", "W3:AP27", "B29:U33", "W29:AP33"]; // 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
});
}
// function to delete all existing protections
function deleteAllProtections() {
var protections = SpreadsheetApp.getActiveSpreadsheet().getProtections(SpreadsheetApp.ProtectionType.SHEET);
protections.forEach(protection => protection.remove());
}
Note:
Note that the first run will need to run deleteAllProtections() so all sheets will have no protections for the first time. Succeeding runs will now skip those sheet with protections.
Reference:
getProtections

How do I automatically remove all protections that exist on a cell in Google Sheets when I (the owner of the workbook) clear its contents?

I'm currently using Google Sheets as a way for people to sign up for certain time shifts. I'm using the following script to automatically protect cells after data entry so that people can't discretely erase their signups without notifying me:
function onEdit(e) {
var range = e.range;
var timeZone = Session.getScriptTimeZone();
var stringDate = Utilities.formatDate(new Date(), timeZone, 'dd/MM/yy HH:mm');
var description = 'Protected on ' + stringDate;
var protection = range.protect().setDescription(description);
// below code taken directly from Google's documentation (second comment is my own):
// Ensure the current user is an editor before removing others. Otherwise, if the user's edit
// permission comes from a group, the script throws an exception upon removing the group.
var me = Session.getEffectiveUser();
//user who installed trigger
protection.addEditor(me);
protection.removeEditors(protection.getEditors());
if (protection.canDomainEdit()) {
protection.setDomainEdit(false);
}
}
The issue at hand is that I am working with many many shifts and people, which makes it very difficult and tedious to remove any protections in the event that someone needs to cancel for valid reasons (google sheets does not provide a way to search through your protected ranges). I understand the logic of writing scripts but am very inexperienced working with them. I could really use some help creating something that will remove all protections on any cell that I clear the data from.
one way would be to open the protect range menu and click the corner. then delete them manually
script would be:
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheets()[0]; // assuming you want the first sheet
var protections = sheet.getProtections();
for (var i = 0; i < protections.length; i++) {
if (protections[i].getDescription() == 'Protect column A') { //name of protected range
protections[i].remove();
}
}
and for mass unprotecting see: https://webapps.stackexchange.com/a/99304/186471
You can get all the protections for a sheet with getProtections [1] and search which protection from all the protections in the sheets is being cleared using getRange method [2] on each Protect object to know the protected range and clear the protection in case the A1 notation is the same as the edited range (the range from the onEdit event), like this:
function onEdit(e) {
var range = e.range;
var newValue = range.getValue();
//If cell's new value is empty then remove protection
if(newValue == '') {
var sheet = range.getSheet();
var protections = sheet.getProtections(SpreadsheetApp.ProtectionType.RANGE);
//Iterate through all sheet's protections
for (var i = 0; i < protections.length; i++) {
var protection = protections[i];
if (protection.getRange().getA1Notation() == range.getA1Notation()) {
protection.remove();
}
}
}
else {
var timeZone = Session.getScriptTimeZone();
var stringDate = Utilities.formatDate(new Date(), timeZone, 'dd/MM/yy HH:mm');
var description = 'Protected on ' + stringDate;
var protection = range.protect().setDescription(description);
// below code taken directly from Google's documentation (second comment is my own):
// Ensure the current user is an editor before removing others. Otherwise, if the user's edit
// permission comes from a group, the script throws an exception upon removing the group.
var me = Session.getEffectiveUser();
//user who installed trigger
protection.addEditor(me);
protection.removeEditors(protection.getEditors());
if (protection.canDomainEdit()) {
protection.setDomainEdit(false);
}
}
}
[1] https://developers.google.com/apps-script/reference/spreadsheet/sheet#getprotectionstype
[2] https://developers.google.com/apps-script/reference/spreadsheet/protection.html#getrange
[3] https://developers.google.com/apps-script/reference/spreadsheet/range#geta1notation

Google sheet conditional formatting and lock cell [duplicate]

I am trying to implement a script that locks a certain range of cells when a condition is true. Here is the link to my document:
https://docs.google.com/spreadsheets/d/1XShGxlz2fA2w2omth-TvYc7cK0nXvVMrhwRKafzVjOA/edit?usp=sharing
Basically I am sharing this document with a group of people so they fill their mail addresses in column B and put a number 1 in column C so it increments my counters for each slot. What I am trying to do is to lock each slot when it is full so other people can no more edit these slots but the problem is with my if statement if (cell1 == 10). The range is always locked even if the if condition is not realized. Here is my code :
function onOpen() {
var ss = SpreadsheetApp.getActive();
var source = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Sheet1");
var cell=60;
var cell1 = source.getRange("G2").getValue();
if (cell1 == 10){
// Protect range B2:B11, then remove all other users from the list of editors.
var ss = SpreadsheetApp.getActive();
var range = ss.getRange('B2:B11');
var protection = range.protect().setDescription('Sample protected range');
// Ensure the current user is an editor before removing others. Otherwise, if the user's edit
// permission comes from a group, the script will throw an exception upon removing the group.
var me = Session.getEffectiveUser();
protection.addEditor(me);
protection.removeEditors(protection.getEditors());
if (protection.canDomainEdit()) {
protection.setDomainEdit(false);
}
}
}
As Andy says in the comments, you need to explicitly remove the protection if cell G2 does not equal 10. (This code removes all protections).
Reading the Protection Class page, where you got the snippets from, I couldn't get a handle on the way editor privileges would factor into your needs, so this script will work if others are editors. If you don't want them to be editors, you'll have to add the relevant code.
function onOpen() {
var ss = SpreadsheetApp.getActive();
var source = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Sheet1");
var cell = source.getRange("G2").getValue();
var range = ss.getRange('B2:B11');
if (cell == 10) {
// Protect range B2:B11 if cell 'G2' = 10
var protection = range.protect().setDescription('Sample protected range');
Logger.log
} else {
// Remove protection if cell 'G2' 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();
}
}
}

Google Script Lock Cells

I am trying to implement a script that locks a certain range of cells when a condition is true. Here is the link to my document:
https://docs.google.com/spreadsheets/d/1XShGxlz2fA2w2omth-TvYc7cK0nXvVMrhwRKafzVjOA/edit?usp=sharing
Basically I am sharing this document with a group of people so they fill their mail addresses in column B and put a number 1 in column C so it increments my counters for each slot. What I am trying to do is to lock each slot when it is full so other people can no more edit these slots but the problem is with my if statement if (cell1 == 10). The range is always locked even if the if condition is not realized. Here is my code :
function onOpen() {
var ss = SpreadsheetApp.getActive();
var source = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Sheet1");
var cell=60;
var cell1 = source.getRange("G2").getValue();
if (cell1 == 10){
// Protect range B2:B11, then remove all other users from the list of editors.
var ss = SpreadsheetApp.getActive();
var range = ss.getRange('B2:B11');
var protection = range.protect().setDescription('Sample protected range');
// Ensure the current user is an editor before removing others. Otherwise, if the user's edit
// permission comes from a group, the script will throw an exception upon removing the group.
var me = Session.getEffectiveUser();
protection.addEditor(me);
protection.removeEditors(protection.getEditors());
if (protection.canDomainEdit()) {
protection.setDomainEdit(false);
}
}
}
As Andy says in the comments, you need to explicitly remove the protection if cell G2 does not equal 10. (This code removes all protections).
Reading the Protection Class page, where you got the snippets from, I couldn't get a handle on the way editor privileges would factor into your needs, so this script will work if others are editors. If you don't want them to be editors, you'll have to add the relevant code.
function onOpen() {
var ss = SpreadsheetApp.getActive();
var source = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Sheet1");
var cell = source.getRange("G2").getValue();
var range = ss.getRange('B2:B11');
if (cell == 10) {
// Protect range B2:B11 if cell 'G2' = 10
var protection = range.protect().setDescription('Sample protected range');
Logger.log
} else {
// Remove protection if cell 'G2' 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();
}
}
}