Protect Sheet when condition is met by change (Google Sheet) - google-apps-script

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

Related

Automatically protect / un-protect sheets or ranges when value is change from another source (google sheets)

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/1st1BjweeahAYrIziAibJgTj-_R_LEfOL5_CwTKg0Pzg/edit#gid=0
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.
Value at Sheet2!A1 is imported from another file with IMPORTRANGE formula. So that I (as administrator) don't have to edit every single Sheet2!A1 from every students to lock their work.
Here is the link to the other master file for me to change the value at every Sheet2!A1 from every student's file: https://docs.google.com/spreadsheets/d/1qXct3QKHyYt-hQR_4-ySjrcrNSPeoai7aogKHuE-14c/edit#gid=0
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 there is "change" (maybe with on change trigger) at Sheet2!A1 (with value imported from another source)?
Note:
1 value at Sheet2!A1 is to lock Sheet1 and 0 value at Sheet2!A1 is to unlock Sheet1.
Is there any formula or google sheet script that can solve this problem?
Issue and solution:
There's no way to trigger a function when the value returned by a formula gets updated (onEdit trigger runs when a user changes the value of a cell).
As an alternative, I'd suggest using a time-based trigger, that will fire periodically (with 1 minute being the highest frequency available).
To do that, you have to install the trigger. You can do that manually, by following these steps, or programmatically, by executing the function installTrigger (see below) once (while logged in with the owner account, who will always have access to the protected range).
This trigger will then fire a function that will check whether Sheet2!A1 is 1, and protect/unprotect the range Sheet1!A:B accordingly (function updateProtection below). See Range.protect to protect the range, and getProtections and Protection.remove to unprotect it.
Code sample:
const SPREADSHEET_ID = "YOUR_SPREADSHEET_ID";
const PROTECTED_RANGE = "A:B";
function updateProtection() {
const ss = SpreadsheetApp.openById(SPREADSHEET_ID);
const sheet1 = ss.getSheetByName("Sheet1");
const protections = sheet1.getProtections(SpreadsheetApp.ProtectionType.RANGE);
const sheet2 = ss.getSheetByName("Sheet2");
const condition = sheet2.getRange("A1").getValue();
if (condition == 1) { // If formula returns 1, protect
const isRangeProtected = protections.some(p => p.getRange().getA1Notation() === PROTECTED_RANGE);
if (!isRangeProtected) { // Avoid duplicate protections
const range = sheet1.getRange(PROTECTED_RANGE);
const protection = range.protect();
const me = Session.getEffectiveUser();
protection.addEditor(me);
protection.removeEditors(protection.getEditors());
if (protection.canDomainEdit()) {
protection.setDomainEdit(false);
}
}
} else { // Unprotect
protections.filter(p => p.canEdit()).forEach(p => p.remove());
}
}
function installTrigger() {
ScriptApp.newTrigger("updateProtection")
.timeBased()
.everyMinutes(1)
.create();
}

How to run script on specific sheet and insert a formula

I'm new and started learning to code this year.
I am trying to insert a formula into a specific sheet. The script is working and I am able to insert a new row and the formula, however the script runs even when I edit a different sheet. I only want it to run when cell A2 is edited on "Test" sheet.
Here is my code:
function onEdit(e) {
insertRow(e);
copyFormulas(e);
function insertRow(e) {
// get sheet
var sheet = SpreadsheetApp.getActive().getSheetByName("Test");
// Get the edited range
var editedRange = e.range;
if (editedRange.getA1Notation() === "A2") {
// check
sheet.insertRowBefore(2);
}
}
function copyFormulas(e) {
SpreadsheetApp
.getActive()
.getSheetByName('Test')
.getRange('C3')
.setFormula("=SUM(A3*B3)");
}
}
LINK to the sheet https://docs.google.com/spreadsheets/d/1AhwnzyKf6dSPHGKUPNuniZ0gtTihPDLaqAyEtAjNn8A/edit?usp=sharing
To accomplish this task, your code can be shortened to the following form
function onEdit(e) {
let range = e.range,
sheet = range.getSheet();
if (sheet.getName() == 'Test' && range.getA1Notation() == 'A2' ){
sheet.insertRowBefore(2);
sheet.getRange('C3').setFormula("=SUM(A3*B3)");
}
}
You can find a lot of useful information by reading the documentation on Event Objects Class Spreadsheet Class Range , or simply by searching for onEdit(e) on this site
I would add, sometimes instead of increasing the amount of formulas on the sheet (which causes the whole table to slow down), it is recommended to use an array formula that works for the whole column, and that you don't have to duplicate on every row.
In the case of your table, instead of adding a formula every time you insert a row with the following code sheet.getRange('C3').setFormula("=SUM(A3*B3)") - it is better to use one array formula in cell C1 ={"Col3";ArrayFormula(if(A2:A<>"",A2:A*B2:B,))}

onEdit specific cell copy data from one google sheets to another

In google sheets, I am trying to get one data to copy from one sheet to another.
I have this code which is working however I would like it to run onEdit when changing cell E4 in Googlesheet1. I am new at this and doesn't seem to get it to quite work with the solutions I found online.
function ExportRange() {
var destination = SpreadsheetApp.openById('googlesheet1');
var destinationSheet = destination.getActiveSheet();
var destinationCell = destinationSheet.getRange("AC3");
var cellData = '=IMPORTRANGE("https://docs.google.com/spreadsheets/googlesheet2", "AE10:AE9697")';
destinationCell.setValue(cellData);
}
Chose between a simple and installable onEdit trigger, depending on your requirements
For most applciaitons a simple onEdit trigger is sufficient, to use it you just need to rename your function ExportRange() to onEdit()
Take advantage of event objetcs that give you informaiton about the event that fired the trigger
So, the trigger onEdit can give you among others information about the event range - that is the range that has been edited
Now you can implement an if statement to specify that the rest of the funciton shall only be run if the event range and the corresponding sheet are as required
Sample:
function onEdit(event) {
var range = event.range;
var sheet = range.getSheet();
if(range.getA1Notation() == "E4" && sheet.getName() == "Googlesheet1"){
var destination = SpreadsheetApp.openById('googlesheet1');
var destinationSheet = destination.getActiveSheet();
var destinationCell = destinationSheet.getRange("AC3");
var cellData = '=IMPORTRANGE("https://docs.google.com/spreadsheets/googlesheet2", "AE10:AE9697")';
destinationCell.setValue(cellData);
}
}
Please note that this function can only be fired by the trigger in
case of an edit. If you try to run it manually, it will give you an
error because event (and thus event.range) will be undefined if
the funciton was not called by an edit event.

Locking cells in Google Sheets at a specific time

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!

Triggering script by specific cell value

I am pretty new to google sheets script development and am wondering how to trigger a clearAll script with cell value i.e. A1=100.
My clearAll script works (see below), though I don't know what to add to it to trigger it using a specific cell value.
function clearAll() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var formresponses1 = ss.getSheetByName("formresponses1");
formresponses1.clearContents();}
Thanks
If you are trying to make it so that whenever somebody puts in the value of "100", it clears the content of the entire sheet, then you can do this:
function onEdit(e) {
var ss = SpreadsheetApp.getActive() //gets the active spreadsheet
var sheet = SpreadsheetApp.getActiveSheet() //gets the active sheet
var cell = ss.getActiveRange() //gets the active cell
var cellContent = cell.getValue() //gets the value of the active cell
if(cellContent === 100) {
sheet.clearContents() //clears the values of the entire active sheet
}
}
If you want to make it so that whenever somebody edits the cell and makes its value "100", the code clears only that cell, then do this:
function onEdit(e) {
var ss = SpreadsheetApp.getActive()
var sheet = SpreadsheetApp.getActiveSheet()
var cell = ss.getActiveRange()
var cellContent = cell.getValue()
if(cellContent === 100) {
cell.setValue("") //clears the value of the active cell
}
}
Of course, in the latter, due to the slight lag in Google scripts, if somebody rapidly puts in 100 in every cell they can, then some of the cells with a value of "100" will stay there, but if the person is putting the values in like a normal person rather than a spammer, then this code will work.
Also, if you are trying to make it so that if the value of a certain cell (ie: A1) is equal to "100," the script clears the entire sheet, do this:
function onEdit(e) {
var ss = SpreadsheetApp.getActive()
var sheet = SpreadsheetApp.getActiveSheet()
var cell = sheet.getRange('A1')
var cellContent = cell.getValue()
if(cellContent === 100) {
sheet.clearContents()
}
}
Hope I could help!
The onEdit trigger runs when any cell in the spreadsheet is edited.
Google Documentation - Spreadsheet On Edit
There is also a change trigger. It is an installable trigger, not a simple trigger.
Available types of triggers
Quote from documentation:
An installable change trigger runs when a user modifies the structure
of a spreadsheet itself — for example, by adding a new sheet or
removing a column.
I think the only thing that will work for you is the On Edit trigger. The trigger gets set from the Resources menu in the Apps Script Code editor.
I don't think you can restrict the code from running only to a certain cell, or a certain value within a cell. The code will run every time you edit ANY cell.
I think that the only other alternative would be to run a time based trigger, and have the script get the value of that cell, and check the value. The shortest time interval you can use is to run a script every minute. So if you edited the cell on second 1, it would take 59 more seconds before anything happened.
If you had some type of user interface, and the value in that cell was written to when the user entered a value in an input field, you could detect that change immediately, and make something happen.