Locking cells in Google Sheets at a specific time - google-apps-script

I want to code my Google Sheets cells in a way that certain cells automatically lock at a specific time every day. I should be able to edit it, but not my collaborators.
How do I pull this off?
In the sample sheet (link here), you will see certain dates (15/7/2015-17/7/2015). I want to code it so that each date (eg A1:B6) is locked daily, 10 pm.

As #NightShadeQueen suggested a combination of Time-driven triggers and the protect()-method of the Class Range can be used.
Open the script editor in the sheet (Tools > Script editor...)
Create code that locks the ranges, might look something like this:
You'll have to modify the code if your sheet doesn't look like the Sample sheet. Especially the lockRanges()-function, which defines which ranges should be protected. Currently, it starts with cell A1, and steps down 7 rows at a time until the end of the sheet is reached.
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName('Sheet1'); //INSERT SHEET NAME HERE
function lockRanges() {
//First cell to lock
var row = 1;
var col = 1;
// Get last row with data in sheet
var lastRow = sheet.getLastRow();
//Loop until last row in sheet is passed
while(row <= lastRow){
//Lock current cell
lockRange(row, col);
// Go to row 7 steps down from current (the next date)
row = row + 7;
}
}
function lockRange(row, col){
var range = sheet.getRange(row, col);
// Create protection object. Set description, anything you like.
var protection = range.protect().setDescription('Protected, row ' + row);
// 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);
}
}
Add the Time-driven trigger
Still in the Script editor; go to Resources > Current project's triggers.
Create a new trigger
Choose Run = lockRanges
Choose Events = Time-driven, Day timer, 10pm to 11pm (or whenever you want)
NOTE: The time may be slightly randomized (read more)
That's it, now the ranges will be protected at the time you chose. As with ranges you protect manually, they show up in Data > Protected sheets and ranges.
Something unclear? Ask away!

Related

Protect Sheet when condition is met by change (Google Sheet)

There is a case where test is given to a certain student. The student can only work with the answer for the test before a given period.
Here is the sample case:
https://docs.google.com/spreadsheets/d/1xS3SMrSh2vHCb-ShP96IzzKQGRXYK7ACKO0Ua4_OHOY/edit?usp=sharing
Sheet1!A:A is where the questions are given
Sheet1!B:B is where the answer must be written by the student
Sheet2!A1 is the condition to protect or unprotect Sheet1. When the date is after 15th, this cell become 1, otherwise it stays 0. This cell change automatically since it contains TODAY() formula.
When Sheet2!A1 change into 1, Sheet1!A:B need to be protected from editing by anyone unless the owner of the file
Is it possible to automatically protect sheet or range (in the example case is Sheet1!A:B) when a condition met?
For instance, if cell A1 in Sheet2 is 0, then Sheet1 is protected from being edited by anyone (excluding me). If cell A1 in Sheet2 is no 0, then Sheet1 is not locked anymore.
Is there any formula or google sheet script that can solve this problem?
You can actually implement that on Sheet using Time Driven Trigger of Apps Script. Time Driven Trigger executes at a particular time. In your case, you have to use Month timer as Type of time based trigger. Month timer has day of month and time of day properties which you can set to determine which day and time the script will run.
Example:
Follow these steps:
Copy and paste the code below to your code editor and save.
function testFunc(){
var e = {
'day-of-month':16
}
lockUnlockSheet(e)
}
function lockUnlockSheet(e) {
var sheet1 = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Sheet1');
if (e['day-of-month'] >= 16) {
var protection = sheet1.protect().setDescription("Protect Sheet1");
var me = Session.getEffectiveUser();
protection.addEditor(me);
protection.removeEditors(protection.getEditors());
if (protection.canDomainEdit()) {
protection.setDomainEdit(false);
}
} else {
sheet1.protect().remove();
}
}
Once you paste the code, click Save
Go to the Triggers tab in your editor (Located on the left side of the editor with alarm clock symbol)
Click Add Trigger
Copy the configuration of the trigger below (You should have 2 triggers generated)
It should look like this:
All set now. But to test the function, we need the help of another function to mimic the behavior of Trigger. This is where we use the testFunc().
In your editor click the dropdown besides the Debug button and change it to testFunc then Click Run.
If you set the value of day-of-month to 16-31, it will lock the Sheet1.
If you change it to 1 - 15 it will unlock it.
Note: Installable triggers always run under the account of the person who created them. The getEffectiveUser() is always you.
Update: Using onEdit Trigger
Code:
function onEditLock(e) {
var range = e.range;
var sheet = range.getSheet();
var sheet1 = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Sheet1');
if (range.getA1Notation() == "A1" && sheet.getName() == "Sheet2") {
if (e.value == 1) {
var protection = sheet1.protect().setDescription("Protect Sheet1");
var me = Session.getEffectiveUser();
protection.addEditor(me);
protection.removeEditors(protection.getEditors());
if (protection.canDomainEdit()) {
protection.setDomainEdit(false);
}
} else if(e.value == 0) {
sheet1.protect().remove();
}
}
}
Trigger Setup:
Output:
Note: onEdit() only runs when a user changes a value in a spreadsheet.
References:
Time-driven events
onEdit events
Installable Triggers
Class Protection
Class Sheet
Class Range

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

Google Sheets: How to automatically insert a word into a cell on a new row detected?

I have searched high and low but I have been unable to find an answer (I am sure I am not explaining it right)
I have a Google Sheet that have multiple sheets (tabs) labeled TabA, TabB and TabC.
On this Google Sheet, I submit a slash command on Slack, which then auto-fills a row on one of the tabs using apps script.
What I am trying to do is simply insert a word called TabA into a specific cell each time a new row has been detected. And insert a word called TabB when a new row has been made on TabB sheet etc.
I am sure I just am typing my questions wrong which is why I am unable to find an answer.
I am not actually sure which part of the code posts to the sheet, I think it is this?
if(sheetName) {
sheetName = sheetName.charAt(0).toUpperCase() + sheetName.slice(1)
} else {
sheetName = "Current"
}
// Find sheet
var sheetFlag = false;
for (var r = 1; r < settings.length; r++) {
if (settings[r][1] == channelID) {
var sheetID = settings[r][2];
var targetChannelID = settings[r][4];
var title = settings[r][0];
sheetFlag = true;
break;
}
}
if (sheetFlag) {
var sheet = SpreadsheetApp.openById(sheetID).getSheetByName(sheetName);
if(!sheet) {
sheet = SpreadsheetApp.openById(sheetID).insertSheet(sheetName);
}
var lastRow = sheet.getLastRow();
var slackDetails = ["", "", text1, "","","","","",realName, new Date(),title,text2];
// paste the slack details to the sheet
sheet.getRange(lastRow + 1,1,1,slackDetails.length).setValues([slackDetails]);```
Thank you in advance
If I understood you correctly, you want to:
Keep track of new rows that are added to each sheet in your spreadsheet (TabA, TabB, TabC).
Write the name of the sheet in successive rows of column D of each sheet every time news rows are detected.
As you were told in the comments, Apps Script has no triggers to track changes made to the spreadsheet by a script. For example, onEdit trigger "runs automatically when a user changes the value of any cell in a spreadsheet".
Workaround (time-based trigger and script properties):
A possible workaround to this limitation is using a time-based trigger that will fire a function periodically. You can create this trigger manually, or programmatically, by running this function once:
function createTrigger() {
ScriptApp.newTrigger("trackRows")
.timeBased()
.everyMinutes(1)
.create();
}
This will fire the function trackRows every minute. This function's purpose is to track changes to each sheet rows since last time it was fired (in this example, 1 minute ago) and write the sheet name to a certain cell if the sheet has more rows with content than during last execution.
To accomplish this, you can use the Properties Service, which lets you store key-value pairs and use them in later executions.
The function trackRows could be something along the following lines:
function trackRows() {
var props = PropertiesService.getScriptProperties();
var ss = SpreadsheetApp.openById("your-spreadsheet-id"); // Please change accordingly
var sheets = ss.getSheets();
sheets.forEach(function(sheet) {
var sheetName = sheet.getName();
var currentRows = sheet.getLastRow();
var oldRows = props.getProperty(sheetName);
if (currentRows > oldRows) {
var firstRow = 2;
var column = 4;
var numRows = sheet.getLastRow() - firstRow + 1;
var rowIndex = sheet.getRange(firstRow, column, numRows).getValues().filter(function(value) {
return value[0] !== "";
}).length;
var cell = sheet.getRange(rowIndex + firstRow, column);
cell.setValue(sheetName);
}
props.setProperty(sheetName, currentRows);
});
}
This function does the following:
Retrieve the script properties that were stored in previous executions.
Get all the sheets in the spreadsheet.
Check the last row with content in each sheet (via Sheet.getLastRow()), and compare the value with the one previously stored in script properties.
If the current last row is higher than the one stored in properties, write the sheet name in the first empty row of column D of the corresponding (starting at D2).
Store the current last row in script properties.
Notes:
The script is adding the sheet name to the first empty row of column D once, if it detects that new rows were added. It's not taking into account how many rows were added since last execution, it only considers if rows were added. This could easily be changed though, if that's what you wanted.
If you want to start from fresh, it would be useful to delete all previously stored properties. To do that, you could run this function once:
function deleteProps() {
var props = PropertiesService.getScriptProperties();
props.deleteAllProperties();
}
Reference:
Class ClockTriggerBuilder
Class PropertiesService
Sheet.getLastRow()

How do I protect a cell range from editing only by owner?

I have a Google Sheet with an onEdit trigger running Google Apps Script code to add cell range protection. The idea is that after editing a cell, the spreadsheet will be locked from editing. You should not be able to delete or modify a cell after adding your data, so the spreadsheet can act as a ledger of sorts.
Anyway, here is the relevant code from the trigger:
// Remove existing protected ranges
var protections = sheet.getProtections(SpreadsheetApp.ProtectionType.RANGE);
for (var i = 0;i<protections.length;i++) {
if (protections[i].getDescription() === sheetname + range) {
protections[i].remove();
}
}
// Add Protected Range to prevent further editing up to the second from last row
var lastRow = SpreadsheetApp.getActiveRange().getLastRow() - 1;
var protectedRange = selectedSheet.getRange("A1:J".concat(lastRow));
var protection = protectedRange.protect().setDescription(sheetName() + ' Protection Range');
// Note: The spreadsheet owner is always able to edit protected ranges and sheets.
var sheetOwner = SpreadsheetApp.getActiveSpreadsheet().getOwner();
protection.addEditor(sheetOwner); // Owner is the only one who can edit.
protection.removeEditors(protection.getEditors()); // Remove all editors.
if (protection.canDomainEdit()) {
protection.setDomainEdit(false);
}
This code successfully adds the protection to the cells, however, it is always Editable by the current logged in user (so the Owner + current user can Edit), thus defeating the protection.
I have tried something like the following:
var sheetOwner = SpreadsheetApp.getActiveSpreadsheet().getOwner();
var me = Session.getEffectiveUser();
protection.addEditor(sheetOwner);
protection.removeEditor(me);
and also something like the following:
protection.addEditors(['email#email.com']);
protection.removeEditors(protection.getEditors());
but it seems like no matter what I do, the current user always gets granted with Edit permissions. Why is that? How do I remove the Edit permissions of the current user for the protected range?
I am not any code writer at all but somehow managed to restrict users from editing once they enter any data in a cell.
I am using below simple code and trigger is on edit. This auto lock a cell once input is given. Just mention email id of all users (whom edit right is given) in remove editors in below code
function LOCKALL() {
var spreadsheet = SpreadsheetApp.getActive();
var protection = spreadsheet.getActiveRange().protect();
protection.setDescription('LOCKALL')
.removeEditors(['mention email id' , 'mention email id']);
};

Class Protection Script

I've taken the information from here
to create a script that will protect a range of cells within sheets from being edited but also excludes areas where a user can enter data. My script looks like this.
function myFunction() {
// Protect the active sheet except B2:C5, then remove all other users from the list of editors.
var sheet = SpreadsheetApp.getActiveSheet();
var protection = sheet.protect().setDescription('2016-07-21 Material Request Form Template');
var unprotected = sheet.getRange('C9:D9');
protection.setUnprotectedRanges([unprotected]);
// 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);
}
}
On the line that reads var unprotected = sheet.getRange('C9:D9'); this will indeed unlock this range as expected within the protected sheet but I also have other ranges within this document that I'd like to restrict. How can I enter or modify this line so that I can lock out multiple ranges i.e. C9:D9 and A12:E12 and A24:K24
If I simply copy this line and paste it with a new cell range, it overwrites the previous unprotected range and activates the new line I just pasted.
Thank you.
For multiple unprotected ranges you need to pass all of the ranges as the array argument in one call:
var range1 = sheet.getRange("A1:A6");
var range2 = sheet.getRange("B1:B6");
protection.setUnprotectedRanges([range1, range2]);
Your question seems to switch and you appear to end up asking how to restrict access to multiple ranges, rather than unprotecting them? In order to do this, protect the whole sheet and then unprotect the relevant ranges to achieve what you need.