Use OnChange function only on the cell that has changed - google-apps-script

I'm using the following script to provide the hex color code of column A:
function onChange(e) {
if (e.changeType == "FORMAT") {
var formula = "=GetCellColorCode";
var tempFormula = "=sample";
var sheet = e.source.getActiveSheet();
sheet.createTextFinder(`^\\${formula}`).matchFormulaText(true).useRegularExpression(true).replaceAllWith(tempFormula);
sheet.createTextFinder(`^\\${tempFormula}`).matchFormulaText(true).useRegularExpression(true).replaceAllWith(formula);
}
}
function GetCellColorCode(input)
{
var ss = SpreadsheetApp.getActiveSpreadsheet();
var cell = ss.getRange(input);
var result = cell.getBackground();
return result
}
I'm using =GetCellColorCode("A"&ROW()) in column G. However, it spans hundreds of rows, and takes too long to update, as every instance of the formula updates when one cell colour background is changed. Is there a way to change this script so only the formula on the row where the cell background colour has changed is updated? I've tried using replaceWith rather than replaceAllWith, but it doesn't update at all. Thanks in advance!
EDIT: For further context, I need this speeding up as I also have a script in there that updates my filter (I want to filter out all green and red rows), but it seems to time out frequently so the filter isn't often updated:
function update_filter() {
var col = 7;
var filter = SpreadsheetApp.getActiveSheet().getFilter();
var criteria = filter.getColumnFilterCriteria(col);
filter.setColumnFilterCriteria(col, criteria);
}

active range usually provides the range where change took place. Try
var sheet1/*renamed from sheet*/ = e.source.getActiveSheet();
var sheet = sheet1.getRange(sheet1.getActiveRange().getRow(),7);
The variable sheet is now of range type, which points to the active row's G column.

Related

Get notes from a cell and insert into another cell

I have a range of cells in Google Sheets, some of them have notes attached.
If there's a note attached to a cell, I need to put the note in a separate cell, and put the location of that note in another cell.
I found this script elsewhere:
function getNote(cell)
{
var ss = SpreadsheetApp.getActiveSpreadsheet();
var range = ss.getRange(cell)
return range.getNote();
}
but when I try to use it I get an error "Exception: Range not found (line 12)."
But this script only gets me halfway there as it only gets the note and puts it in a cell. I also need to know what cell the note came from.
Any help is greatly appreciated.
In the script above the function getNote() expects the paramter cell
If you just run the script without calling getNote() from another function / an environment where it gets a values for cell assigned, the script will fail wiht the error you obtained.
Indeed, this script doe snot not meet your needs. What you probably want is to screen all your cells for the one that have notes.
What you need to decide is into which cells you want to put the note and the cell notation.
Below is a sample that you need to adapt for your needs:
function getNotes() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
//if you have only one sheet in the spreadsheet, otherwise use ss.getSheetByName(name);
var sheet = ss.getActiveSheet();
var range = sheet.getDataRange();
var results = range.getNotes();
for (var i = 0; i < results.length; i++) {
for (var j = 0; j < results[0].length; j++) {
//if a not empty note was found:
if(results[i][j]){
var note = results[i][j];
var cell = range.getCell(i+1, j+1);
var notation = cell.getA1Notation();
//adjust the offset as function of the column / row where you want to output the results
cell.offset(0, 1).setValue(note);
cell.offset(0, 2).setValue(notation);
}
}
}
}
Important references:
getNotes()
getA1Notation()
getDataRange()
getCell(row, column)
offset(rowOffset, columnOffset)

Google Sheets Script - Reference a specific cell in a row

I have a sheet where when I change a specific cell to "YES", I need a template sheet to be copied to a new version and named as per the value of a cell on the current row.
I'm having trouble working out how to get the value of the first cell in the row selected. This is what I have so far (I know this is wrong):
function onEdit() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheets()[0];
var currentCell = sheet.getCurrentCell();
if (currentCell = "YES")
{
SpreadsheetApp.getActiveSpreadsheet().toast("New change control sheet added to workbook.","Change Control",15);
var sourceRow = ss.getActiveRange().getRowIndex();
var tabName = ss.getRange(cell,1).getValues();
ss.getSheetByName("CCTemplate").showSheet()
.activate();
ss.setActiveSheet(ss.getSheetByName('CCTemplate'), true);
ss.duplicateActiveSheet();
ss.setActiveSheet(ss.getSheetByName('CCTemplate'), true);
ss.getActiveSheet().hideSheet();
ss.setActiveSheet(ss.getSheetByName('Copy of CCTemplate'), true);
ss.getActiveSheet().setName("CC" & tabName);
}
}
Any ideas?
function onEdit(e) {
var sh=e.range.getSheet();
if(sh.getName()=='Your Sheet Name' && e.value=="YES") {
e.source.toast="New change control sheet added to workbook.","Change Control",15);
var tabName=sh.getRange(e.range.rowStart,1).getValue();
var tsh=e.source.getSheetByName('CCTemplate');
var csh=tsh.copyTo(e.source);
csh.setName('CC'+tabName);
}
}
You should avoid using activate in your scripts especially in simple triggers where you have to finish in 30 seconds. I think this code does the same thing that you intended for your code. One significant difference is that I use the information that comes in the event object that comes with the trigger. You should add the code Logger.log(JSON.stringify(e)) and then look at the logs you will see that there is a lot of information available to you which removes the need to run extra functions to get things like a spreadsheet.
Use event objects
onEdit offers among others the event objects range and value which are helpful to retrieve the range that has been edited and its value.
Also
When you want to a cell and compare it against a value, like in if (currentCell = "YES") - you need to retrive its value (either currentCell.getValue() or just event.value) and you need to use == instead of = for comparison.
Be careful with getValues() vs getValue(). The former gives you a 2D array and is not necessary if you want to retrieve the value of a single cell.
There is no need to set your sheet to active in order to change its name.
You can rewrite your code as following:
function onEdit(event) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheets()[0];
var currentCell = event.range;
var value = event.value;
if (value == "YES")
{
...
var sourceRow = range.getRowIndex();
var tabName = ss.getRange(sourceRow, 1).getValue();
...
ss.getSheetByName('Copy of CCTemplate').setName("CC" + tabName);
}
}

Is there a way to dynamically reference cells in scripts for google sheets via script editor to create a note on another cell?

I want to be able to quickly and easily add notes (Insert menu / Note) on multiple Google Sheets cells using cell values on other columns on that sheet. For example, F3 would grab the cell values in X3, F4 from X4. But also, if I grab G3, it would grab the values in Y3 (so it consistently grabs the values on the same row, but always the same number of columns to the right (in this case, always 18 columns to the right). I want to grab those values and stick them into a note on the left (so, columns F, G, etc.). I would like to be able to highlight a range (F3-F10), run the Google script, and it will grab all values on X3-X10 and stick them on their relative rows. Is this possible?
I've been hunting for this option, searching for every range of terms I can think of, and I've got pretty close, but I keep getting stuck at numerous points, the most important being able to grab the data from an equal number of columns to the right, relative to the range I highlight. I found this code that gets me close, but I don't know how to edit it to do what I need. It allows me to create the menu option, which works on pulling the value of A1 on the first tab of my spreadsheet (I'm working on the second tab, and it's only grabbing the same data for all of my notes). Here is the code I've been working on, but also a second function that I think has features that I need, such as grabbing the active sheet.
function noteSetter() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheets()[0]; //I need to make this grab the right sheet
var cell = sheet.getRange(2, 2); //this and the line below it seem to be critical here...
cell.setNote(sheet.getRange(1,1).getValue());
}
//below gives me the menu option to run the script, but I'm open to changing this
function onOpen() {
var sheet = SpreadsheetApp.getActiveSpreadsheet();
var entries = [{
name : "Set cell note",
functionName : "noteSetter"
}];
sheet.addMenu("Scripts", entries);
};
Here is another one that is used on that post that has features that I think I need, but still doesn't get me the relative reference of the cells.
function onEdit(e) {
var sheet = SpreadsheetApp.getActiveSheet();
if (sheet.getName() == "Project"){ //Change to your sheet name
var sourceRange = e.source.getActiveRange();
var sourceRow = sourceRange.getRow();
var sourceColumn = sourceRange.getColumn();
var sourceValue = sourceRange.getValue();
if(sourceColumn == 1){ //Change to the column you want to get the value from
var noteCell = sheet.getRange(sourceRow, 2); //The cell where the Note should be, B-column on the current row
noteCell.setNote(sourceValue);
}
}
}
This is where I am now, but I may have messed some things up, as it no longer works at all.
function noteSetter() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheets()[0];
var cell = sheet.getActiveRange();
cell.setNote(sheet.getRange(sourceRow, sourceColumn).getValue());
}
function onOpen() {
var sheet = SpreadsheetApp.getActiveSpreadsheet();
var entries = [{
name : "Set cell note",
functionName : "noteSetter"
}];
sheet.addMenu("Scripts", entries);
};
Any suggestions on how to make this work?
Just to close the loop on this a little, I'm 90% of the way there with the below. It works perfectly when I highlight one cell. But if I highlight more (e.g. a range or array), it sticks the only the top and left-most value as the note in all cells I've highlighted.
function noteSetter() {
var spreadsheet = SpreadsheetApp.getActive();
var sheet = spreadsheet.getActiveSheet();
var row = sheet.getActiveRange().getRow();
var column = sheet.getActiveRange().offset(0, 18);
var cells = sheet.getActiveRange();
cells.setNote((row, column).getValue());
}
I think the next step is using the Selection class, but it's good enough for my purposes.

Optimization for alternate background rows color in Google Spreadsheet

I set a different background color for the even rows from the Spreadsheet sheet using Google Apps Script but it is very very very slow...any ideas how can I optimize it?
Example Spreadsheet: https://docs.google.com/spreadsheets/d/1yRotjooCRpuuSTjjgFEzw4xxqPLJwMZJPchYQeNvUyw/edit?usp=sharing
See the GS code by going to Tools -> Script Editor...
This is the code:
function myFunction() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var range = ss.getRange("A1:Z1000");
ss.setActiveRange(range);
var totalRows = SpreadsheetApp.getActiveRange().getNumRows();
var totalColumns = SpreadsheetApp.getActiveRange().getNumColumns();
var startRow = SpreadsheetApp.getActiveRange().getRow();
var startColumn = SpreadsheetApp.getActiveRange().getColumn();
var sheet = SpreadsheetApp.getActiveSheet();
var row = startRow;
while (row < totalRows+startRow)
{
var column = startColumn;
while (column < totalColumns+startColumn){
if(row%2 == 0){
sheet.getRange(row, column).setBackground("#F3F3F3");
}
column++;
}
row++;
}
}
You can use new applyRowBanding():
Applies a default row banding theme to the range. By default, the banding has header and no footer color.
Example:
function setColors() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var range = ss.getRange("A1:Z1000");
// first remove any existing alternating colors in range to prevent error "Exception: You cannot add alternating background colors to a range that already has alternating background colors."
range.getBandings().forEach(banding => banding.remove());
// apply alternate background colors
range.applyRowBanding();
}
Result:
For more options also see
applyRowBanding(bandingTheme)
applyRowBanding(bandingTheme, showHeader, showFooter)
Go line by line instead of cell by cell.
You can also loop directly on the lines you want to change.
function setBackgroundColorOnEvenLines() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var totalRows = sheet.getMaxRows();
var totalColumns = sheet.getMaxColumns()
for (var i=2; i <= totalRows; i+=2){
sheet.getRange(i, 1, 1, totalColumns).setBackground("#F3F3F3");
}
}
This reference shows how to reference an entire line like above.
While you've asked for a Google Apps Script solution, I prefer to set a conditional formatting by formula rather than do this as a scripted solution - I feel it's less complicated, faster and more intuitive.
=mod(A1,2)=0
Will format all even rows in the range defined.
You might also like to look into this call:
https://developers.google.com/apps-script/reference/spreadsheet/range#getbackgrounds
and its sister call
https://developers.google.com/apps-script/reference/spreadsheet/range#setbackgroundscolor
This returns a two dimensional array for the entire range of all background colours.
Using this you can get an array of the whole range, step through it in increments of two, and set all of the elements of each row to the alternate background colour you wish (perhaps using one of the methods described here: Javascript | Set all values of an array). Then at the end call setBackgroundColors(color) to set the colors.
This way, you only make one call to actually update the spreadsheet at the end - and that's the part that will take the most time and which will have slowed down your original code the most.

going through each cell in a column and reset its value if >0

When I open a google sheet, I'd like all values within one specific column to be reset to 0 if its value is >0.
Please note that some cells contain no data, I wouldn't like to mess up with these cells as they should stay empty.
At the moment I'm really trying to build up an MVP so if I have to hardcode the value of the column within the formula it's OK.
I've made some research and tried to find relevant examples here, I've tweaked one snippet which works for one specific cell, now I'd like to learn how to automate the execution of this script so I don't have to hardcode each cell....
Here is the script I have
function onOpen() {
var doc = SpreadsheetApp.getActiveSpreadsheet();
var cell = doc.getRange("B4");
var value = cell.getValue();
if (value > 0) {
cell.setValue("0");
}
else {
cell.setValue("0");
}
}
I'd really appreciate if you could help me a bit with this one by either poiting me in the right direction or showing me a working example.
Thanks
This code works:
//function onOpen() {
function makeZero() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var theSheet = ss.getSheetByName('Sheet 2');
var theLastRow = theSheet.getLastRow();
//Get all values from column A
var theColumnRng = theSheet.getRange(1, 1, theLastRow, 1);
//Get all the values from column A. The return from getValues()
//is a two dimensional array
var theColumnValues = theColumnRng.getValues();
var thisCellValue = "";
var i;
for (i=0;i<theColumnValues.length;i++) {
//Every inner array only has one element, therefore, index zero
thisCellValue = theColumnValues[i][0];
if (thisCellValue > 0) {
theColumnValues[i][0] = 0;
}; //No "else" condition needed. If not greater than zero, do nothing
};
//Reset the entire column's values all at once
theColumnRng.setValues(theColumnValues);
};
If you want to compare it with another column.
Please select sheet name, i (row ) number and col number accordingly (i,2) 2 is a col here. IF there is no col then put Val2=0 in your case. Also don't forget to edit the setValue also.
function ifelse_col_comparision(){
var ss= SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Sheet1');
for(var i=2;i<ss.getLastRow();i++){
var val1=ss.getRange(i,2).getValue();
var val2=ss.getRange(i,3).getValue();
if(val2 >= val1){ss.getRange(i,4).setValue('Bigger or 0');}
else{ss.getRange(i,4).setValue('lower or -1');}
}
}
Please ask if you are looking for something else.