Google Spreadsheet: run script on cell value change - google-apps-script

In a spreadsheet I have two sheets, * and Calculator. When I change the value on "*"!B1 (i.e. cell B1 of sheet *), I want to run the script: to change (actually clear) the value of "Calculator"!B3 (i.e. cell B3 of sheet "Calculator").
Here's my code. I created it through Spreadsheet > Tools > Script editor, so it's not an standalone:
The difference between Version 1 and 2 is that in Version 1 the first function is run and the second is missed (vice versa in Version 2), plus one thing I'll detail below.
Version 1
function onEdit(e) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var languageSheet = ss.getSheetByName("*").getSheetName();
var languageCell = languageSheet.getRange("B1");
var sheets = ss.getSheets()[0];
var languageCellCheck = sheets.getActiveCell();
var cookingMethodSheet = ss.getSheetByName("Calculator").getSheetName();
var cookingMethodCell = cookingMethodSheet.getRange("B3");
var range = e.range;
if (range == languageCell)
cookingMethodCell.setValue("A");
}
//function onEdit(e) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var languageCell = ss.getSheetByName("*").getRange("B1");
var sheets = ss.getSheets()[0];
var languageCellCheck = sheets.getActiveCell();
var cookingMethodCell = ss.getSheetByName("Calculator").getRange("B3");
var range = e.range;
if (range == languageCell)
cookingMethodCell.setValue("A");
//}
Version 2
//function onEdit(e) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var languageSheet = ss.getSheetByName("*").getSheetName();
var languageCell = languageSheet.getRange("B1");
var sheets = ss.getSheets()[0];
var languageCellCheck = sheets.getActiveCell();
var cookingMethodSheet = ss.getSheetByName("Calculator").getSheetName();
var cookingMethodCell = cookingMethodSheet.getRange("B3");
var range = e.range;
if (range == languageCell)
cookingMethodCell.setValue("A");
//}
function onEdit(e) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var languageCell = ss.getSheetByName("*").getRange("B1");
var sheets = ss.getSheets()[0];
var languageCellCheck = sheets.getActiveCell();
var cookingMethodCell = ss.getSheetByName("Calculator").getRange("B3");
var range = e.range;
if (range == languageCell)
cookingMethodCell.setValue("A");
}
When I say "Version 1" I mean I changed the value of "*"!B1 from "Something" to "Whatever", and cell "Calculator"!B3 wasn't changed. Then, using Version 2, I tried the same but got the same result (no change in B3).
The idea of having two functions (same for both versions) is because I don't know if using ss.getSheetByName("Calculator").getSheetName(); or directly selecting in as in ss.getSheetByName("*").getRange("B1"); will make a difference. Apparently, it doesn't.
What's and where's the error?

Going by your variable name var languageSheet = ss.getSheetByName("*").getSheetName(); it seems that you expect languageSheet to actually be a sheet object and not a string. That seems to be reaffirmed var sheets = ss.getSheets()[0];
As such you must edit that line to say var languageSheet = ss.getSheetByName("*") and the same goes further on with var cookingMethodSheet = ss.getSheetByName("Calculator")
Now the real problem:
if (range == languageCell)
cookingMethodCell.setValue("A");
this will always return false. You are comparing two different objects. They will never be the same. You need to compare their column and row or A1 notation instead by using something like range.getColumn() and range.getRow() or range.getA1Notation(). Currently it's like you write the number 3 on two pieces of paper and ask if the two pieces of paper are the same thing. They will both have the same value and be comparable, but they will never be the same piece of paper.
Furthermore, going by your code, you are getting the edited range and then you get the active cell which will always be the same cell. So you might as well have the logic be if (1 == 1) or more specifically, because you are comparing to range type objects, it's like writing if (1 == 2)
EDIT: With the things considered in the comments of this answer you are making things more complicated then they need to be. Here is a function that will clear B1:B3 in Calculator only if B1 in * is changed:
function onEdit(e) {
var langName = '*'
var langCell = 'B1'
var curSheet = e.range.getSheet()
if (curSheet.getName() === langName) {
if (e.range.getA1Notation() === langCell) {
var cookingMethodSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Calculator')
var range = cookingMethodSheet.getRange('B1:B3')
range.clear()
}
}
which you can make a little shorter by doing
function onEdit(e) {
if (e.range.getSheet().getName() === '*') {
if (e.range.getA1Notation() === 'B1') {
SpreadsheetApp
.getActiveSpreadsheet()
.getSheetByName('Calculator')
.getRange('B1:B3')
.clear()
}
}
The indentation is there only so the line is not too long.

Related

apps script - search value from sheet1 in array in sheet2 (google sheets)

I'm completely new to apps script and java.
I have a tracker where users insert IDs in col1 sheet1. I need to be able to prevent and alert them whenever their input DOESN'T exists in an array in col2 sheet 2.
so far I've got this, but this is not working for me at all.
var ss = SpreadsheetApp.getActiveSpreadsheet();
function onEdit(e) {
var sheet = ss.getSheetByName("Sheet1")
var so = ss.getSheetByName("Sheet2"); so.activate();
var vltest = sheet.getActiveCell().getValue()
var vlary = so.getActiveCell().getValue()
var r = sheet.getActiveRange().getRow()
var c = sheet.getActiveRange().getColumn();
if(vltest!=="" && c==1 && r>1 && sheet == "Sheet1"){
var data = so.getRange("B1:B").getValues().filter(String).flat()
if(data.indexOf(vlary)=data.lastIndexOf(vlary)){
e.range.setValue("")}
if(data.indexOf(vlary)=data.lastIndexOf(vlary)){SpreadsheetApp.getUi().alert("Alert", "Alert", SpreadsheetApp.getUi().ButtonSet.OK);}}}
Any ideas?
You need to get all values from compare sheet, loop them through and look for match of the value, that onEdit() gives you back. Something like this:
function onEdit(e) {
var ss = SpreadsheetApp.openById('sheet_ID'); //or getActive();
var so = ss.getSheetByName("Sheet2");
// let's say you get all rows from column 1
var compareData = so.getRange(1, 1, so.getLastRow(), 1).getValues();
for (var d = 0; d < compareData.length; d++) {
if (compareData[d] === e.value) {
SpreadsheetApp.getUi().alert("Alert", "Alert", SpreadsheetApp.getUi().ButtonSet.OK)
}
}

Script is triggering but nothing happens?

I want to create a script that moves data from one sheet to another when I mark it as completed in a particular column. Using some other code I found on the internet, I have this, but when I go in and change that status to completed nothing happens. The trigger page in google apps script says it's executing, but it isn't doing anything to the actual sheet. Here is the code:
function onEdit(e) {
if(SpreadsheetApp.getActiveSpreadsheet() == "Planner" && e.value == "Completed"){ //If the edit was on Planner marking the Status "Completed"
var spr = SpreadsheetApp.getActiveSpreadsheet();
var myRange = e.range.offset(0,-3,0,3).getValue() //get the information from Planner
//find the first row of Calendar where completed assignments is blank
var column = spr.getRange('O:O');
var values = column.getValues(); // get all data in one call
var ct = 0;
while ( values[ct][0] != "" ) {
ct++;
ct++;
e.source.getSheetByName("Calendar").getRange(ct,15,1,3).setValues(myRange).getValues(); //copy the values from Planner to Calendar
e.source.getSheetByName("Planner").getRange(myRange).setValues("").getValues(); //delete values from Planner
;}
return (ct);
}
}
I assume something is wrong with it but I don't know what. I've never used apps script before so I honestly don't know what I'm doing. Here is the sheet:
Sheet
I want to move completed homework from the planner sheet to the calendar sheet when I change the status. Thanks so much for any help!!
EDIT:
I used lamblichus's code and it works great except that I still want to delete the data from the Planner Sheet after I move it. I tried this code and it didn't work:
function onEdit(e) {
const ss = e.source;
const range = e.range;
const sheet = range.getSheet();
if (sheet.getName() == "Planner" && e.value == "Completed") {
var otherData = range.offset(0,-3,1,3).getValues();
var currentClass = range.offset(0,-4).getMergedRanges()[0].getValue();
var [task,,date] = otherData[0];
var targetSheet = ss.getSheetByName("Calendar");
var targetRange = targetSheet.getRange("O1").getNextDataCell(SpreadsheetApp.Direction.DOWN).offset(1,0,1,3);
targetRange.setValues([[date,task,currentClass]]);
var initialSheet = ss.getSheetByName("Planner");
var initialRange = initialSheet.range.offset(0,-3,1,3);
initialRange.clearContent(); //delete values from Planner
}
}
Issues and solution:
There are several issues with your current code:
If you want to check the sheet name, you have to use Sheet.getName(). SpreadsheetApp.getActiveSpreadsheet() just returns the active spreadsheet, not sheet, and not its name anyway.
If you want to get values from multiple cells, you should use getValues(), not getValue().
The third parameter of offset corresponds to the number of rows of the resulting range. Therefore, it should not be 0.
The "Class" name is in a merged range, and only the top-left cell in a merged range includes the corresponding value. To get that value, you can use getMergedRanges and retrieve the first element in the resulting array. Since getValue() returns the value in the top-left cell of a range, it will return the "Class" name.
Code sample:
function onEdit(e) {
const ss = e.source;
const range = e.range;
const sheet = range.getSheet();
if (sheet.getName() == "Planner" && e.value == "Completed") {
var otherDataRange = range.offset(0,-3,1,3);
var otherData = otherDataRange.getValues();
var currentClass = range.offset(0,-4).getMergedRanges()[0].getValue();
var [task,,date] = otherData[0];
var targetSheet = ss.getSheetByName("Calendar");
var targetRange = targetSheet.getRange("O1").getNextDataCell(SpreadsheetApp.Direction.DOWN).offset(1,0,1,3);
targetRange.setValues([[date,task,currentClass]]);
otherDataRange.clearContent();
}
}
It looks like a syntax error on line 14, you put ;}, it should be }; you don't need to tell JavaScript (the coding language that AppScript is based on) when you end comments. But it likes it when you tell it when you end while loops.
Here is the updated code.
function onEdit(e) {
if(SpreadsheetApp.getActiveSpreadsheet() == "Planner" && e.value == "Completed"){ //If the edit was on Planner marking the Status "Completed"
var spr = SpreadsheetApp.getActiveSpreadsheet();
var myRange = e.range.offset(0,-3,0,3).getValue() //get the information from Planner
//find the first row of Calendar where completed assignments is blank
var column = spr.getRange('O:O');
var values = column.getValues(); // get all data in one call
var ct = 0;
while ( values[ct][0] != "" ) {
ct++;
ct++;
e.source.getSheetByName("Calendar").getRange(ct,15,1,3).setValues(myRange).getValues(); //copy the values from Planner to Calendar
e.source.getSheetByName("Planner").getRange(myRange).setValues("").getValues(); //delete values from Planner
};
return (ct);
};
}

How to refresh a filter onEdit() in Google sheets?

I have a sheet where I fill data with Hlookups depending on the values I choose in dropdowns.
I want to filter (hide) the rows that have a NULL or blank value in column 3 each time I change the values in the dropdowns (which changes the whole dataset).
If I create a normal filter, it doesn't refresh when the data changes.
var PARAMETER_ROW_NUMBER = 5; //The parameters goes from Row 1 to this Row
var PARAMETER_COLUMN_NUMBER = 2; //The column where the dropdowns with the parameters for the VLOOKUPs are
function onEdit()
{
var thisSheet = SpreadsheetApp.getActiveSheet();
if( thisSheet.getName() == "By Place" )
{
var cell = thisSheet.getActiveCell();
var cellRow = cell.getRow();
var cellColumn = cell.getColumn();
if( cellColumn == PARAMETER_COLUMN_NUMBER && cellRow <= PARAMETER_ROW_NUMBER)
{
setFilter(); // Execute the filter to clean null rows each time I change the values in the dropdowns
var rowDiff = PARAMETER_ROW_NUMBER - cellRow;
cell.offset( 1, 0, rowDiff).setValue(''); // As the parameters are dependent dropdowns, I clear the dropdowns if one changes
}
}
}
function setFilter()
{
var ss = SpreadsheetApp.getActiveSheet();
var rang = ss.getDataRange();
var filtercriteria = SpreadsheetApp.newFilterCriteria().setHiddenValues([' ','']).build();
var filter = rang.getFilter() || rang.createFilter();
filter.setColumnFilterCriteria(3, filtercriteria); // I want to hide the rows which has a null or blank in column 3
}
The setFilter() function doesn't work.
The array you're using to set the hidden values is not correct, try your code like this:
function setFilter()
{
var ss = SpreadsheetApp.getActiveSheet();
var rang = ss.getDataRange();
var filterCriteria = SpreadsheetApp
.newFilterCriteria()
.setHiddenValues(['NULL', ''])
.build();
var filter = rang.getFilter() || rang.createFilter();
filter.setColumnFilterCriteria(3, filterCriteria);
}
Also, if you want to see the logs from your onEdit executions, you can check them on your Apps Script file by clicking in View - > Executions, there you will be able to see the errors you are getting.
Docs
I used these docs to help you:
setColumnFilterCriteria(columnPosition, filterCriteria).
Class FilterCriteriaBuilder.

Google Apps Script to replace formula with text in the current row

I'm trying to write a script in google sheets so that if I change a cell in column F, it will copy and paste values on the same row in columns b:e. I'd appreciate any help.
I have a google sheet where operators log events. They type in a reference number in column a and it looks up corresponding data (from another tab) and displays it in columns b-e. Then they add data like their name, the current time, etc in columns F-M. I'm trying to write a script so that when I change column F it copies the results from the formulas in columns b-e in the current row and pastes them back in place as values.
The reason is two-fold, one removing the formulas and pasting values improves performance and two if someone changed the lookup data the row becomes corrupted.
I found the following script that inserts a timestamp - seems like minor modifications would work but I haven't been able to figure it out. (thank you to the author of this)
//CORE VARIABLES
// The column you want to check if something is entered.
var COLUMNTOCHECK = 1;
// Where you want the date time stamp offset from the input location. [row, column]
var DATETIMELOCATION = [0,11];
// Sheet you are working on
var SHEETNAME = 'ReceivingLog'
function onEdit(e) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
//checks that we're on the correct sheet.
if( sheet.getSheetName() == SHEETNAME ) {
var selectedCell = ss.getActiveCell();
//checks the column to ensure it is on the one we want to cause the date to appear.
if( selectedCell.getColumn() == COLUMNTOCHECK) {
var dateTimeCell = selectedCell.offset(DATETIMELOCATION[0],DATETIMELOCATION[1]);
dateTimeCell.setValue(new Date());
}
}
}
Partially solved: The second function (below) works as a stand-alone to copy paste values, but it doesn't like the two functions strung together like this. Looking for help on how to have two scripts function together.
//CORE VARIABLES
// The column you want to check if something is entered.
var COLUMNTOCHECK = 1;
// Where you want the date time stamp offset from the input location. [row, column]
var DATETIMELOCATION = [0,11];
// Sheet you are working on
var SHEETNAME = 'ReceivingLog'
function onEdit(e) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
//checks that we're on the correct sheet.
if( sheet.getSheetName() == SHEETNAME ) {
var selectedCell = ss.getActiveCell();
//checks the column to ensure it is on the one we want to cause the date to appear.
if( selectedCell.getColumn() == COLUMNTOCHECK) {
var dateTimeCell = selectedCell.offset(DATETIMELOCATION[0],DATETIMELOCATION[1]);
dateTimeCell.setValue(new Date());
}
}
}
//CORE VARIABLES
// The column you want to check if something is entered.
var COLUMNTOCHECK2 = 2;
// Where you want the date time stamp offset from the input location. [row, column]
var DATETIMELOCATION2 = [0,-1];
// Sheet you are working on
var SHEETNAME2 = 'PurchaseOrders'
function onEdit2(e) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
//checks that we're on the correct sheet.
if( sheet.getSheetName() == SHEETNAME2 ) {
var selectedCell = ss.getActiveCell();
//checks the column to ensure it is on the one we want to cause the date to appear.
if( selectedCell.getColumn() == COLUMNTOCHECK2) {
var dateTimeCell = selectedCell.offset(DATETIMELOCATION2[0],DATETIMELOCATION2[1]);
var data = dateTimeCell.getValues()
dateTimeCell.setValue(data) , {contentsOnly: true};
}
}
}

Migrating text formatting from a cell to another cell that references it

In one of the sheets of a google spreadsheet there is a cell which I am referencing in another cell. The text gets copied fine but without any of the formatting. So if I have any bold or italic writing it will show as normal writing in the referenced cell.
I have tried the below script but it's not working as it should. Instead of copying the whole formatting it copies just the cell borders and it copies it to multiple cells below as well(to a total of 7 cell, the target one plus 6 below).
function onEdit() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var activeSheet = ss.getActiveSheet();
var activeSheetName = activeSheet.getName();
var activeCell = activeSheet.getActiveCell();
if ( activeSheet.getName().indexOf("Job ID") != -1 && activeCell.getRow() == 4 && activeCell.getColumn() == 15 ) {
var targetSheet = ss.getSheetByName('Active Jobs');
var jobRowNumber = findJobIdRow();
var sourceCell = activeSheet.getRange(4,1,15,1);
sourceCell.copyFormatToRange(targetSheet, 16, 16, jobRowNumber, jobRowNumber);
}
}
function findJobIdRow() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var jobID = ss.getActiveSheet().getRange(2,1).getValue();
var column = ss.getSheetByName('Active Jobs').getRange(2,1,ss.getSheetByName('Active Jobs').getMaxRows()-2,1);
var values = column.getValues(); // get all data in one call
for(var ct = 0; ct < values.length-1; ct++){
if(values[ct][0] == jobID){
var ct = ct+2;
break;
}
}
return ct;
}
First, your invocation of getRange may contain an error:
you write
var sourceCell = activeSheet.getRange(4,1,15,1);
but this range is not a single cell, it's a 15-by-1 range starting from A4. The order is (row, column, numRows, numColumns). Perhaps you meant
var sourceCell = activeSheet.getRange(4,15,1,1);
Second, I suggest using copyTo method, which has options that supersede the functionality of "copy...ToRange" methods.
var targetRange = targetSheet.getRange(...);
sourceCell.copyTo(targetRange, {formatOnly: true});