setbackground not working in google sheets [duplicate] - google-apps-script

I have created the following simple function:
function test(r,c) {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
sheet.getRange(r,c).setBackground("red");
return 1;
}
In the spreadsheet, I write "=test(row(),column())
This results in ERROR with the following message:
Error: You do not have permission to call setBackground (line 3).
It is NO problem if I create another function call in the script as follows:
function test_the_test(){
test(5,4);
}
Why can't I call the test function from the spreadsheet cell?
Thank you in advance

As it is clearly explained in the documentation, Custom functions return values, but they cannot set values outside the cells they are in. In most circumstances, a custom function in cell A1 cannot modify cell A5. That is of course also true for other methods such as setBackground etc.

It's not possible to call anything which sets content from cell, but it is possible to call it from buttons.
Actually is quite easy. Definitely it's not true that you can't change others cell content.
The trick is to not call the function from cell but mount it into drawing/image.
Click insert -> drawing
Draw a image and save it (you should see
your image in spreadsheet)
Click it by right mouse button - in top right corner, there is a little
triangle opening options
Click Assign script and type name of your
script without parenthesis (like "test" not "test()" ) and confirm
Click the button. A pop-up window asking for privileges to access spreadsheet appears.
Confirm it, if problem with refresh occurs just refresh it manually (F5)
Now you can click the button and you can edit any cell you like
This code work fine when mounted to button.
function test() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
sheet.getRange(3,5).setBackground("red");
}

The current version of Google Sheets (Jan 2022) uses the Apps Script editor and allows you to put scripts in a container file that automatically attaches to your Google Sheet.
You can open the Apps Script editor from the Google Sheets menu under Extensions -> App Script
In the default Code.gs editor file you can simply extend a default event hook such as onEdit() and put in logic to filter your actions to a specific set of circumstances. Here is a simple example of using this hook:
function onEdit(e) {
if( ! e ){
return;
}
var currentSheet = e.source.getActiveSheet();
var currentRange = e.range;
// only want action to occur when a single cell changes
if( currentRange.getNumRows() === 1 && currentRange.getNumColumns() === 1 ){
var currentColumn = currentRange.getLastColumn();
var currentRow = currentRange.getLastRow();
// only want action to occur for a column on a certain sheet
var myTargetSheet = "Sheet 1";
var myTargetColumn = 1;
if( currentSheet.getName() == myTargetSheet && currentColumn == myTargetColumn ){
// set background color for the selected row based on a lookup
var cellValue = currentRange.getCell(1,1).getValue();
var assignedColor = myCustomSearch( cellValue );
currentSheet.getRange( "A" + currentRow + ":E" + currentRow ).setBackgroundColor( assignedColor );
}
}
}
function myCustomSearch( searchTerm ){
var assignedColor = "#ffffff";
var lookupSheet = SpreadsheetApp.getActive().getSheetByName("Sheet 2");
var lookupRange = lookupSheet.getRange("H1:H20"); // where the search terms live
var numColumns = lookupRange.getNumColumns();
var numRows = lookupRange.getNumRows();
var lookupList = lookupRange.getValues();
for( var myColumn=0; myColumn < numColumns; myColumn++ ){
for( var myRow=0; myRow < numRows; myRow++ ){
if( lookupList[myRow][myColumn] == searchTerm ){
assignedColor = lookupSheet.getRange("H"+(myRow+1)).getBackgroundColor();
break;
}
}
}
return assignedColor;
}

Related

onSelectionChange re-triggers itself when changing tab

I've been trying to get a simple function to work for a couple of days but I can't see where I'm going wrong. I would really appreciate some help is possible please.
Background:
I have a calendar tab with a table containing Stores down the side and Dates across the top.
When the user clicks between E7:AI200 I want to be able to:
Grab the Store Name from column 1 and the clicked row and populate B1 with the value.
Grab the Date from row 1 and the clicked column and populate C1 with the value.
This seems to work fine.
I also want the spreadsheet to automatically navigate to another tab called 'Filtered Events' when the cell between E7:AI200 clicked.
This also seems to work fine.
Issue: When I click back to the Calendar tab it seems to re-click the original cell and navigates me straight back to the 'Filtered Events' tab and I am stuck in a loop of continously going between tabs each time I try to get back to the Calendar.
function onSelectionChange(e) {
var ss = SpreadsheetApp.getActive();
if (ss.getActiveSheet().getName() == "Calendar") {
var sheet = ss.getSheetByName("Calendar");
var full_range = sheet.getRange("A1:AI200");
var click_range = e.range;
var row_lookup = click_range.getRow();
var col_lookup = click_range.getColumn();
var selected_store = full_range.getCell(row_lookup, 1).getValue();
var selected_date = full_range.getCell(1, col_lookup).getValue();
var populate_store = sheet.getRange("B1");
var populate_date = sheet.getRange("C1");
if (row_lookup > 6 && row_lookup < 250 && col_lookup < 36 && col_lookup > 4) {
populate_store.setValue(selected_store);
populate_date.setValue(selected_date);
var change_sheet = ss.getSheetByName("Filtered Events");
change_sheet.activate();
}
else {
populate_store.setValue('');
populate_date.setValue('');
}
}
}
Probably you can use PropertiesService to store the last used coordinates of selected cell and check them at the start of the script:
function onSelectionChange(e) {
var ss = SpreadsheetApp.getActive();
if (ss.getActiveSheet().getName() == "Calendar") {
var sheet = ss.getSheetByName("Calendar");
var full_range = sheet.getRange("A1:AI200");
var click_range = e.range;
var row_lookup = click_range.getRow();
var col_lookup = click_range.getColumn();
// try to get the last used cell coordinates
var last_cell_row = PropertiesService.getScriptProperties().getProperty('last_cell_row');
var last_cell_col = PropertiesService.getScriptProperties().getProperty('last_cell_col');
// save current cell coordinates
PropertiesService.getScriptProperties().setProperty('last_cell_row', row_lookup);
PropertiesService.getScriptProperties().setProperty('last_cell_col', col_lookup);
// if the coordinates are the same break the function
if (row_lookup == last_cell_row && col_lookup == last_cell_col) return;
// the rest of your code ...
I haven't tested it. It would be better if you provide some dummy data for testing, how your sheets look like.

Automatic triger not executing, manually executing works perfectly

My script works perfectly when I execute it manually through the editor but fails to fire with the edit or change trigger.
I saw a bunch of people having the same problem but none of these solutions help me solving this issue.
Has anybody a solution for this ?
function createNewSheets() {
// 1. Retrieve the current sheet names.
var dataSheet = SpreadsheetApp.getActiveSpreadsheet();
var sheetNames = dataSheet.getSheets().map(s => s.getSheetName());
// 2. Retrieve the values from "mastersheet" sheet.
var masterSheet = dataSheet.getSheetByName('Liste de nageurs');
var values = masterSheet.getRange('A2:B' + masterSheet.getLastRow()).getValues();
// 3. Using the retrieved sheet names and values, the new sheets are inserted and the values are put.
values.forEach(r => {
if (!sheetNames.includes(r[0])) {
var newSheet = dataSheet.insertSheet(r[0])
sheetNames.push(r[0]);
newSheet.appendRow(r);
}
});
}
I ran it this way, using a checkbox in A1:
function onEdit(e) {
//e.source.toast('entry');//just a toast to let me know it's work
const sh = e.range.getSheet();// getting sheet name
//next line limits execution to Sheet1 A1 being changed to 'TRUE'
if (sh.getName() == 'Sheet1' && e.range.columnStart == 1 && e.range.rowStart == 1 && e.value == 'TRUE') {
e.range.setValue('FALSE');//reset the checkbox back to 'FALSE'
//the rest is your code
var ss = SpreadsheetApp.getActive();
var names = ss.getSheets().map(s => s.getSheetName());
var masterSheet = ss.getSheetByName('Sheet2');//changed sheet name
var vs = masterSheet.getRange('A2:B' + masterSheet.getLastRow()).getValues();
vs.forEach(r => {
if (!names.includes(r[0])) {
var newSheet = ss.insertSheet(r[0])
names.push(r[0]);
newSheet.appendRow(r);
}
});
}
}
You can simplify your code if you learn how to use the event object better but I left most of your code intact. The problem that most users dislike about using the event object is that you can't debug the code by running it from the script editor because you have to supply the event object as a parameter. I normally debug them but running them from the trigger. You can use JSON.stringify(e) to look at the event object.
Using the event object normally results in faster code and simple triggers must finish in 30 seconds or less.

How to make an automatic timestamp in google sheets?

I have data in 6 columns and then a column "Last updated" where I manually write the date of the last change in the row. I am sure there is a way to automatize this, but cannot make it work.
note - I do not now how to code, just tried to modify multiple codes I found on the web and nothing worked for me, I am getting desperate.
It is basically a database of chemical substances, where each column is a property, and I need to stamp the last time any of the properties were changed. Can anyone please help me with this?
You will have to enter some code in order to make this work. This link gives a good example on how to create a script for your sheet.
The most important part of your script is that you want it to run EVERY time you make a change to the sheet in ANY cell. Then when it runs, you can check which cell was changed and them determine if that cell is in the area you're interested in checking. Consider a sheet set up like this one:
When you have your sheet set up, then go to the Tools menu and select Script Editor. From there you'll enter the code below. What I'm giving you is an example that you must change to meet your needs. The first part to understand is the function onEdit. This is a specially named function that will run EVERY time a change is made to the sheet. So when you have
function onEdit(eventObj) {
}
Then anything you put inside the braces will execute each time a change is made (after the user types Enter). So your onEdit function looks like this:
function onEdit(eventObj) {
//--- you could set up a dynamic named range for this area to make it easier
var thisSheet = SpreadsheetApp.getActiveSheet();
var checkRange = thisSheet.getRange("B2:D5");
if (isInRange(checkRange, eventObj.range)) {
Logger.log('cell is in range');
var propertyCell = eventObj.range;
var timestampCell = thisSheet.getRange(propertyCell.getRow(), 5);
timestampCell.setValue(Utilities.formatDate(new Date(), "UTC+8", "MM-dd-yyyy hh:mm:ss"));
} else {
Logger.log('must be outside the range');
}
The eventObj is the cell that the user edited and changed. So we are using that cell to compare with our area to check. If we determine that the cell is within that area, then we go ahead and define the cell to hold the timestamp and then assign a value to it. The value is the formatted date and time.
The important piece here is the checkRange variable that is defined as the area of all your properties. So when the onEdit function runs, it defines the area you're checking, then calls isInRange to see if the cell that was edited is within that area. Here is the function:
function isInRange(checkRange, targetCell) {
Logger.log('checking isInRange');
//--- check the target cell's row and column against the given
// checkrange area and return True if the target cell is
// inside that range
var targetRow = targetCell.getRow();
if (targetRow < checkRange.getRow() || targetRow > checkRange.getLastRow()) return false;
Logger.log('not outside the rows');
var targetColumn = targetCell.getColumn();
if (targetColumn < checkRange.getColumn() || targetColumn > checkRange.getLastColumn()) return false;
Logger.log('not outside the columns');
//--- the target cell is in the range!
return true;
}
Your result will look like this:
So now, all together, just copy and paste this code into your script editor:
function isInRange(checkRange, targetCell) {
Logger.log('checking isInRange');
//--- check the target cell's row and column against the given
// checkrange area and return True if the target cell is
// inside that range
var targetRow = targetCell.getRow();
if (targetRow < checkRange.getRow() || targetRow > checkRange.getLastRow()) return false;
Logger.log('not outside the rows');
var targetColumn = targetCell.getColumn();
if (targetColumn < checkRange.getColumn() || targetColumn > checkRange.getLastColumn()) return false;
Logger.log('not outside the columns');
//--- the target cell is in the range!
return true;
}
function onEdit(eventObj) {
//--- you could set up a dynamic named range for this area to make it easier
var thisSheet = SpreadsheetApp.getActiveSheet();
var checkRange = thisSheet.getRange("B2:D5");
if (isInRange(checkRange, eventObj.range)) {
Logger.log('cell is in range');
var propertyCell = eventObj.range;
var timestampCell = thisSheet.getRange(propertyCell.getRow(), 5);
timestampCell.setValue(Utilities.formatDate(new Date(), "UTC+8", "MM-dd-yyyy hh:mm:ss"));
} else {
Logger.log('must be outside the range');
}
}
It's not necessary to use scripts. It may be easier to use the formula:
=(your_datetime - date(1970;1;1) ) * 86400000
timestamp-to-date-and-back-screenshot
Check the example here: https://docs.google.com/spreadsheets/d/1JHr4z3-32fWYUAjm3bG_Wyr-ZXn7FUvrECa3B8TtJtU/edit?usp=sharing

Get the user that changed specific cell

I am trying to make spreadsheet with table that could by edited by anyone who is invited. However user can write only into cells that are empty or are filled by him. He cannot overwrite somebody's else work.
I was thinking about saving the editor emails and their cell notations into Properties, however with Session.getActiveUser().getEmail(), SpreadsheetApp.getActive().getActiveRange().getA1Notation() and trigger onEdit I cannot differentiate between two people, if there were adding something at the same time, I would't be able to tell who is doing what... at least I think that is how it works.
Thanks
Session.getActiveUser() is not accesible in the onEdit trigger if you have a normal gmail account (source: https://developers.google.com/apps-script/reference/base/session#getActiveUser())
But I found a very cool workaround :)
The tric lies in the fact that you can not remove yourself and the owner as Editors. So if you remove all editors from a protected range, you are assigning yourself as the editor (and the owner).
In this script, the owner of the spreadsheet can overrule everybody. The others will behave as you wished: they can only edit their own entries and empty fields.
// Test it with colors
// var edittedBackgroundColor = "RED"; // makes the change visible, for test purposes
// var availableBackgroundColor = "LIGHTGREEN"; // makes the change visible, for test purposes
function onEdit(e) {
Logger.log(JSON.stringify(e));
var alphabet = "abcdefghijklmnopqrstuvwxyz".toUpperCase().split("");
var columnStart = e.range.columnStart;
var rowStart = e.range.rowStart;
var columnEnd = e.range.columnEnd;
var rowEnd = e.range.rowEnd;
var startA1Notation = alphabet[columnStart-1] + rowStart;
var endA1Notation = alphabet[columnEnd-1] + rowEnd;
var range = SpreadsheetApp.getActive().getRange(startA1Notation + ":" + endA1Notation);
if(range.getValue() === "") {
Logger.log("Cases in which the entry is empty.");
if(typeof availableBackgroundColor !== 'undefined' && availableBackgroundColor)
range.setBackground(availableBackgroundColor)
removeEmptyProtections();
return;
}
// Session.getActiveUser() is not accesible in the onEdit trigger
// The user's email address is not available in any context that allows a script to run without that user's authorization, like a simple onOpen(e) or onEdit(e) trigger
// Source: https://developers.google.com/apps-script/reference/base/session#getActiveUser()
var protection = range.protect().setDescription('Cell ' + startA1Notation + ' is protected');
if(typeof edittedBackgroundColor !== 'undefined' && edittedBackgroundColor)
range.setBackground(edittedBackgroundColor);
// Though neither the owner of the spreadsheet nor the current user can be removed
// The next line results in only the owner and current user being able to edit
protection.removeEditors(protection.getEditors());
Logger.log("These people can edit now: " + protection.getEditors());
// Doublecheck for empty protections (if for any reason this was missed before)
removeEmptyProtections();
}
function removeEmptyProtections() {
var ss = SpreadsheetApp.getActive();
var protections = ss.getProtections(SpreadsheetApp.ProtectionType.RANGE);
for (var i = 0; i < protections.length; i++) {
var protection = protections[i];
if(! protection.getRange().getValue()) {
Logger.log("Removes protection from empty field " + protection.getRange().getA1Notation());
protection.remove();
}
}
return;
}
function isEmptyObject(obj) {
for(var prop in obj) {
if(obj.hasOwnProperty(prop))
return false;
}
return JSON.stringify(obj) === JSON.stringify({});
}

Cell coloring in google-spreadsheet fails if it is called from a cell, but works ok when called from the script.

I have created the following simple function:
function test(r,c) {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
sheet.getRange(r,c).setBackground("red");
return 1;
}
In the spreadsheet, I write "=test(row(),column())
This results in ERROR with the following message:
Error: You do not have permission to call setBackground (line 3).
It is NO problem if I create another function call in the script as follows:
function test_the_test(){
test(5,4);
}
Why can't I call the test function from the spreadsheet cell?
Thank you in advance
As it is clearly explained in the documentation, Custom functions return values, but they cannot set values outside the cells they are in. In most circumstances, a custom function in cell A1 cannot modify cell A5. That is of course also true for other methods such as setBackground etc.
It's not possible to call anything which sets content from cell, but it is possible to call it from buttons.
Actually is quite easy. Definitely it's not true that you can't change others cell content.
The trick is to not call the function from cell but mount it into drawing/image.
Click insert -> drawing
Draw a image and save it (you should see
your image in spreadsheet)
Click it by right mouse button - in top right corner, there is a little
triangle opening options
Click Assign script and type name of your
script without parenthesis (like "test" not "test()" ) and confirm
Click the button. A pop-up window asking for privileges to access spreadsheet appears.
Confirm it, if problem with refresh occurs just refresh it manually (F5)
Now you can click the button and you can edit any cell you like
This code work fine when mounted to button.
function test() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
sheet.getRange(3,5).setBackground("red");
}
The current version of Google Sheets (Jan 2022) uses the Apps Script editor and allows you to put scripts in a container file that automatically attaches to your Google Sheet.
You can open the Apps Script editor from the Google Sheets menu under Extensions -> App Script
In the default Code.gs editor file you can simply extend a default event hook such as onEdit() and put in logic to filter your actions to a specific set of circumstances. Here is a simple example of using this hook:
function onEdit(e) {
if( ! e ){
return;
}
var currentSheet = e.source.getActiveSheet();
var currentRange = e.range;
// only want action to occur when a single cell changes
if( currentRange.getNumRows() === 1 && currentRange.getNumColumns() === 1 ){
var currentColumn = currentRange.getLastColumn();
var currentRow = currentRange.getLastRow();
// only want action to occur for a column on a certain sheet
var myTargetSheet = "Sheet 1";
var myTargetColumn = 1;
if( currentSheet.getName() == myTargetSheet && currentColumn == myTargetColumn ){
// set background color for the selected row based on a lookup
var cellValue = currentRange.getCell(1,1).getValue();
var assignedColor = myCustomSearch( cellValue );
currentSheet.getRange( "A" + currentRow + ":E" + currentRow ).setBackgroundColor( assignedColor );
}
}
}
function myCustomSearch( searchTerm ){
var assignedColor = "#ffffff";
var lookupSheet = SpreadsheetApp.getActive().getSheetByName("Sheet 2");
var lookupRange = lookupSheet.getRange("H1:H20"); // where the search terms live
var numColumns = lookupRange.getNumColumns();
var numRows = lookupRange.getNumRows();
var lookupList = lookupRange.getValues();
for( var myColumn=0; myColumn < numColumns; myColumn++ ){
for( var myRow=0; myRow < numRows; myRow++ ){
if( lookupList[myRow][myColumn] == searchTerm ){
assignedColor = lookupSheet.getRange("H"+(myRow+1)).getBackgroundColor();
break;
}
}
}
return assignedColor;
}