Google Sheets - Script to change cell value based on several cells values - google-sheets

I have been searching for a while and trying to work together a script from various answered topics that will allow me to adjust an adjacent cells content based on the data entered. I cannot seem to get it to work properly and need some help steering the ship the right direction. Here is what I am trying to accomplish:
--If the value of cell A2:A is a six digit number AND the value of cell D2:D (same row) is "MATCH" then the value for cell B2:B should be set to "ANN"
--If the value of cell A2:A is a six digit number AND the value of cell D2:D (same row) is "NO MATCH" then the value for cell B2:B should be set to "ANN" and a drop-down data validation list of ['ANN','RNW'] populate WITH the default value of the list set to "ANN"
--If the value of cell A2:A has a length of seven or greater characters then a drop-down data validation list of ['1DY','RNW','NEW'] populate WITH the default value of the list set to "1DY"
Is it even possible to set the value of a data validation cell to a specific, default value? This is important as when the user is entering data they will more than likely accept the default value. If they don't want the default value then they can select a value from the drop-down list.
I built a test sheet which shows the what the sheet should look like when data is filled out in column A and the associated values in column B.
My test is here: https://docs.google.com/spreadsheets/d/1p8sq63S-vSU1FKFLjtr2ZypItN5viXotoZL0Ki2PoQM/edit?usp=sharing
Here is the cobbled together script I was attempting to build (I too find it funny). This is my first attempt to right a Google Script to run on a spreadsheet.
function onEdit() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var aSheet = ss.getActiveSheet();
var aCell = aSheet.getActiveCell();
var aColumn = aCell.getColumn();
var aRow = aCell.getRow();
//var licenseStatus = aSheet.getRange(aRow, aColumn+9).getValue();
// The row and column here are relative to the range
// getCell(1,1) in this code returns the cell at B2, B2
var licenseTypeCell = aSheet.getRange(aRow, aColumn+1);
if (aColumn == 1 && aSheet.getName() == 'Onsite') {
if (isnumber(aCell) && (len(aCell) <= 6)) {
var rule = SpreadsheetApp.newDataValidation().requireValueInList(['ANN','RNW']).build();
licenseTypeCell.setValue("ANN");
licenseTypeCell.setDataValidation(rule);
} else {
var rule = SpreadsheetApp.newDataValidation().requireValueInList(['1DY','RNW','NEW']).build();
licenseTypeCell.setValue("1DY");
licenseTypeCell.setDataValidation(rule);
}
}
}
Any help/guidance would be greatly appreciated.

You are on the right track, few minor changes. Below you will find some new function to be used in your code.
1) getValue() You get your cell using var aCell = aSheet.getActiveCell() i.e the cell that was edited. But to get the value of the cell you will need to do the following aValue = aCell.getValue()
2) isNaN() To check if the aValue (as determined above) is a number or not. You will use a function called isNaN(aValue). Google script uses javascript platform and hence we need to use functions from javascript. This is different from an inbuilt function you use in a google spreadsheet. It returns True if the value is Not A Number(NAN). Hence, we use a not operator(!) to flip the return value, like so
if(!isNaN(aValue))
3) Number of digits There is no len function in google scripts, hence to determine if the number is 6 digits long you can do the following
if(aValue < 1000000)
Your final code will look something like this:
function onEdit() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var aSheet = ss.getActiveSheet();
var aCell = aSheet.getActiveCell();
var aColumn = aCell.getColumn();
var aRow = aCell.getRow();
//var licenseStatus = aSheet.getRange(aRow, aColumn+9).getValue();
// The row and column here are relative to the range
// getCell(1,1) in this code returns the cell at B2, B2
var licenseTypeCell = aSheet.getRange(aRow, aColumn+1);
var aValue = aCell.getValue()
if (aColumn == 1 && aSheet.getName() == 'Main') {
if (!isNaN(aValue) && aValue < 1000000) {
var matchCell = aSheet.getRange(aRow, aColumn+3).getValue()
//The above gets value of column D (MATCH or NO MATCH)
if(matchCell == "MATCH"){ //Check if Col D is MATCH
licenseTypeCell.setValue("ANN");
}
else{
var rule = SpreadsheetApp.newDataValidation().requireValueInList(['ANN','RNW']).build();
licenseTypeCell.setValue("ANN");
licenseTypeCell.setDataValidation(rule);
}
} else {
var rule = SpreadsheetApp.newDataValidation().requireValueInList(['1DY','RNW','NEW']).build();
licenseTypeCell.setValue("1DY");
licenseTypeCell.setDataValidation(rule);
}
}
}
Also, note the addition of the following lines to check for col D Value
var matchCell = aSheet.getRange(aRow, aColumn+3).getValue()
//The above gets value of column D (MATCH or NO MATCH)
if(matchCell == "MATCH"){ //Check if Col D is MATCH
licenseTypeCell.setValue("ANN");
}

Related

Google sheets How to create a custom function

I have two sheets
Sheet1
and sheet2
What I want to do is one by one copy the values of each row from sheet 1 to sheet 2. whenever one row is copied from sheet1 to sheet2 (i.e values from cell A to F). the cell G2 and H2 (in sheet2) get values ( based on multiple different formulas filter function, pivit tables etc from other sheets ) When I get the updated values in sheet 2 I want to copy the values form G2 and H2 to the respective row back in sheet 1
basically I want to use the sheet2 as a custom function where cell A2, B2, C2, D2, H2, F2 are inputs and cell G2 and H2 are outputs.
I tried using macros but what is required is a combination of both Absolute and Relative referencing and that doesn't seem to be possible
gif of the process I am doing manually
Did it with appscript and menue items
function onOpen() {
var ui = SpreadsheetApp.getUi();
// Or DocumentApp or FormApp.
ui.createMenu('AERON')
.addItem('Calculate Single Cabnets', 'menuItem1')
.addSeparator()
.addItem('Calculate Multiple Cabnets', 'menuItem2')
.addToUi();
}
function menuItem2() {
var sheet = SpreadsheetApp.getActiveSheet();
var selection = sheet.getSelection();
if(selection.getActiveRange().getA1Notation()==null)
ui.alert('No range selected');
else{
//get the inputs
var sheetname = SpreadsheetApp.getActiveSheet().getName();
// SpreadsheetApp.getUi().alert(sheetname);
var range = SpreadsheetApp.getActiveSpreadsheet().getRange(selection.getActiveRange().getA1Notation());
var input = range.getValues();
var ac = SpreadsheetApp.getActiveSpreadsheet().getActiveRange()
SpreadsheetApp.getUi().alert(ac);
// get sheet name
sheetname = SpreadsheetApp.getActiveSheet().getName()
const numRows = ac.getNumRows();
for (let i=1; i<= numRows;i++ ){
var cell = ac.getCell(i, 1).offset(0, 0,1,6).getA1Notation();
var cell1 = ac.getCell(i, 1).offset(0, 7,1,2).getA1Notation();
// SpreadsheetApp.getUi().alert(cell + " " +sheetname + " " + cell1);
single_row(sheetname, cell, cell1 );
}
SpreadsheetApp.getUi().alert("All values updated")
}
}
function single_row(sheetName, inputRange, outputRange ) {
var fnSheetName = "Main";
var fnInputRange = 'A2:F2';
var fnOutputRange = 'G2:H2';
var input = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(sheetName).getRange(inputRange).getValues();
SpreadsheetApp.getActiveSpreadsheet().getSheetByName(fnSheetName).getRange(fnInputRange).setValues(input);
var output = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(fnSheetName).getRange(fnOutputRange).getValues();
SpreadsheetApp.getActiveSpreadsheet().getSheetByName(sheetName).getRange(outputRange).setValues(output);
}
This creates a menue item on the top which one by one copies the values to the other sheet then copies the result back

Google App Scripts Copy row and Sort range using custom function

I am using a modified version of this code. Where the goal is to copy/move a row to another sheet based on a dropdown value. I addition when the dropdown value is changed in sheet 2, after the row has been moved the row should be copied back to sheet 1.
I do have some working code, but it breaks the Don't repeat yourself principle, because the code is repeating it self multiple times. That's why I tried to create a custom function moveWhenDone(); where only the targetSheet variable is unique and is passed as a argument.
For some reason, this code does not work when put in the function and I don't understand why?
Another issue I am facing is regarding the sortSheets(); function, both sheet 1 and sheet 2 have a filter on range A:J sorted by column 1. After the row is copied I need the sheets to re-sort to reflect the changes. For some reason this ONLY works on sheet 1, after I edit a cell in the sheet 1. For sheet 2, no sorting, even after a cell edit.
How can both the sheets be re-sorted after the row is copied?
UPDATE
Link to test sheet
function onEdit(event) {
// assumes source data in sheet 1 named ASSIGMNMENTS
// target sheet 2 of move to named DONE
// getColumn with check-boxes is currently set to colu 7
var act = SpreadsheetApp.getActiveSpreadsheet();
var src = event.source.getActiveSheet();
var r = event.source.getActiveRange();
if(src.getName() == "ASSIGMNMENTS" && r.getColumn() == 7 && r.getValue() == "Done") {
// Working but breaks the "Don't repeat yourself" principle
/*var row = r.getRow();
var numColumns = src.getLastColumn();
var targetSheet = act.getSheetByName("DONE");
var target = targetSheet.getRange(targetSheet.getLastRow() + 1, 1);
src.getRange(row, 1, 1, numColumns).copyTo(target);
src.deleteRow(row);*/
var targetSheet = act.getSheetByName("DONE");
moveWhenDone(targetSheet);
} else if(src.getName() == "DONE" && r.getColumn() == 7 && r.getValue() != "Done") {
// Working but breaks the "Don't repeat yourself" principle
/*var row = r.getRow();
var numColumns = src.getLastColumn();
var targetSheet = act.getSheetByName("ASSIGMNMENTS");
var target = targetSheet.getRange(targetSheet.getLastRow() + 1, 1);
src.getRange(row, 1, 1, numColumns).copyTo(target);
src.deleteRow(row);*/
var targetSheet = act.getSheetByName("ASSIGMNMENTS");
moveWhenDone(targetSheet);
}
sortSheets();
}
// Only work on ASSIGMNMENTS (sheet 1)
function sortSheets() {
SpreadsheetApp.flush();
var acticeSheet = SpreadsheetApp.getActiveSpreadsheet();
acticeSheet.getRange("A2:Z").sort([{column: 1, ascending: true}]);
}
// Do not work when called from onEdit
function moveWhenDone(targetSheet) {
var source = event.source.getActiveSheet();
var row = r.getRow();
var numColumns = source.getLastColumn();
var target = targetSheet.getRange(targetSheet.getLastRow() + 1, 1);
source.getRange(row, 1, 1, numColumns).copyTo(target);
source.deleteRow(row);
}
Here are the issues why your code is not working:
You are using event and r variables in your moveWhenDone() function which is not defined. event and r variables are only accessible in onEdit() unless you pass it as an argument to your moveWhenDone()
The reason why your sort function only works on the active sheet is because you used SpreadsheetApp.getActiveSpreadsheet(); which returns a Spreadsheet object. When getRange() is used, it will get the range on the active sheet in your spreadsheet unless you provide the sheet name as part of your A1 Notation (example, "Done!A2:Z")
By the way, I didn't get your point when you mentioned "Don't repeat yourself principle". It somehow contradicts in your statement where "when the dropdown value is changed in sheet 2, after the row has been moved the row should be copied back to sheet 1."
Here is a sample code to move row data from sheet1 to sheet2 and vise-versa including the sorting:
function onEdit(event) {
// assumes source data in sheet 1 named ASSIGMNMENTS
// target sheet 2 of move to named DONE
// getColumn with check-boxes is currently set to colu 7
var act = SpreadsheetApp.getActiveSpreadsheet();
var src = event.source.getActiveSheet();
var r = event.source.getActiveRange();
if(src.getName() == "ASSIGMNMENTS" && r.getColumn() == 7 && r.getValue() == "Done") {
// Working but breaks the "Don't repeat yourself" principle
var row = r.getRow();
var numColumns = src.getLastColumn();
var targetSheet = act.getSheetByName("DONE");
var target = targetSheet.getRange(targetSheet.getLastRow() + 1, 1);
src.getRange(row, 1, 1, numColumns).copyTo(target);
src.deleteRow(row);
sortSheets()
} else if(src.getName() == "DONE" && r.getColumn() == 7 && r.getValue() != "Done") {
// Working but breaks the "Don't repeat yourself" principle
var row = r.getRow();
var numColumns = src.getLastColumn();
var targetSheet = act.getSheetByName("ASSIGMNMENTS");
var target = targetSheet.getRange(targetSheet.getLastRow() + 1, 1);
src.getRange(row, 1, 1, numColumns).copyTo(target);
src.deleteRow(row);
sortSheets()
}
}
function sortSheets() {
SpreadsheetApp.flush();
var sheetNames = ["ASSIGMNMENTS", "DONE"];
sheetNames.forEach(sheet => {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(sheet);
sheet.getRange("A2:H").sort([{column: 1, ascending: true}]);
});
}
Modifications done:
I removed moveWhenDone() since I didn't fully understood its purpose to prevent "Don't repeat yourself principle"
Instead of sorting the sheets every time the onEdit() is triggered, I put the sort function when a row was moved to a different sheet.
I modified the sortSheets() to loop specific sheets listed in sheetNames variable. I changed the range to "A2:H" because the sample sheet provided has mismatch in number of columns. (ASSIGNMENTS sheet has columns A-Y, while DONE sheet has columns A-M, Hence range "A2:Z" cannot be used)
Sample Output:
If you really want to make moveWhenDone() work, you can pass the event and r variables in onEdit(). Sample moveWhenDone(targetSheet, event, r).
But keep in mind that if you do that, what will happen is that you will basically move the current row twice to your destination sheet.
Example:
If you changed Assignment 2 to "Done", its succeeding row will also be moved to another sheet. Hence both Assignment 2 and 5 will be moved even if Assignment 5 is not yet "Done"

Setting a column to equal the negative of a row in Google Sheets

The Google Sheets API seems vague and I'm probably just too tired.
function onEdit(e) {
var sheet = SpreadsheetApp.getActiveSpreadsheet();
var positives = sheet.getRange("D3:AG3");
var negatives = sheet.getRange("C4:C33");
for (i=0;i<positives.getLastColumn();i++) {
var j = positives[i]*-1;
negatives[i].setValue(j);
}
}
I'm sure I'm doing eight things wrong but if someone is more familiar with Google Sheets, please throw a brick at me.
First, positives is a ranges, and you need to use getValues() to get an array that you can manipulate.
Second, it's not recommended to use Sheets API methods inside loops, the best practice is to manipulate arrays in loops and then use single get and set values API to read / write to a range.
Sample Code:
function onEdit(e) {
var sheet = SpreadsheetApp.getActiveSpreadsheet();
var positives = sheet.getRange("D3:AG3").getValues();
var negatives = sheet.getRange("C4:C33");
var result = [];
for (i = 0; i < positives[0].length; i++) {
result.push([positives[0][i] * -1]);
}
negatives.setValues(result);
}
Sample Output: (I only put values in three rows)
Reference:
push()
Avoid using onEdit for these kind of changes as it will be resource intensive. You are changing all the values of the column into negative of the row EVERY TIME you edit the sheet (Unless that should be the case)
If you really want to use onEdit, be sure to limit it only when the specific range is edited.
Code:
function onEdit(e) {
const row = e.range.getRow();
const column = e.range.getColumn();
// if edited range is within D3:AG3
if(row == 3 && column >= 4 && column <= 33) {
// write to the corresponding row (invert col and row)
e.source.getActiveSheet().getRange(column, row).setValue(e.value * -1);
}
}
Note:
Behaviour of the onEdit function is that when you edit the range D3:AG3, it will negate its value and write into its corresponding destination, one by one.
If you edit D3, it will assign that negative value into C4, nothing more.
If you edit outside the positive range, it will not do anything.
Another approach is to copy your positive row into negative column by transforming your data structure into the destination by bulk.
Code:
function rowToColumn() {
var sheet = SpreadsheetApp.getActiveSpreadsheet();
var pRange = sheet.getRange("D3:AG3");
var pValues = pRange.getValues();
// pValues is a 2D array now
// row range values = [[1, 2, 3, ...]
var negatives = sheet.getRange("C4:C33");
// column range values = [[1], [2], [3], ...]
// since structure of row is different than column
// one thing we can do is convert the row into column structure
// and multiply each element with -1, then assign to negatives
pValues = pValues.map(function(item) {
item = item.map(function(col) {
return [col * -1];
});
return item;
})[0];
// set values into the negatives range
negatives.setValues(pValues);
}
Note:
Behaviour of the rowToColumn function is that it transfers all the values of the row range and then put it into negatives range all at once.
Blank cells will yield 0 by default, add a condition on return [col * -1]; if you want blank cells to return other values instead.
Output:

Radio button in Google Sheets formula

So I've created sort of radio button chooser table with check boxes so I can pick which line I want and that brings the value from this line out to the result cell...
this is the basic principle:
this way which ever checkbox I click, the value of that cell becomes 1 (TRUE) rather than 0 (FALSE), and using SUMPRODUCT formula, every line is multiplied by the boolean checkbox so if the checkbox is not checked, the production is zeroed, and if the checkbox is checked, the value remains.
In this example on B8 I get:
B8 = A2*B2 + A3*B3 + A4*B4 = 0*3 + 1*8 + 0*12 = 8
That works well as long as the values are numbers..
Now I'm trying to do the same but with string values..
something like this:
Any idea?
Solution:
Use IF to get corresponding Col B
JOIN the resulting array
Sample:
=ARRAYFORMULA(TEXTJOIN(",",1,IF(A2:A4,B2:B4,)))
I used the checkbox and Google Apps Script, where column A (1) and column B (2) contain the checkboxes. For example:
function onEdit(evt) {
var range = evt.range;
var val = range.getValue();
var row = range.getRow();
var col = range.getColumn();
// -------------------------------------
// --- Cannot be both add and delete ---
// -------------------------------------
if (col == 1) {
if (val) {
SpreadsheetApp.getActiveSheet().getRange(row,2).setValue('FALSE');
}
}
else if (col == 2) {
if (val) {
SpreadsheetApp.getActiveSheet().getRange(row,1).setValue('FALSE');
}
}
}
I would like to share my codes. Please run onStart() for setting up radio boxes as well.
function onEdit(e) {
// radio button
var ss = SpreadsheetApp.getActiveSpreadsheet();
var gsheetrange = gsheet.getDataRange().getValues();
var rowcount = gsheetrange.length; // find end of row
for(var i = 0; i <= rowcount; i++) { // start from 0.
if (i==gedited){
gsheet.getRange(gedited, 2).setValue("true");
}
else
{
gsheet.getRange(i, 2).setValue("false");
}
}
}

Google sheets- loop search column, select tow

I have a google spreadsheet that i have one last problem i cant seem to solve.
i added a button to this script, and when i press the button it triggers the AddClient function.
How can i make the script below loop down all rows in column 3 searching for the yes value, when it finds it, copy the row below it to sheet "client" and then stop?
function AddClient(event) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var s = event.source.getActiveSheet();
var r = event.source.getActiveRange();
if(s.getName() == "SETUP" && r.getColumn() == 3 && r.getValue() == "yes") {
var row = r.getRow() + 1; // Add 1 to the active row
var targetSheet = ss.getSheetByName("client");
var target = targetSheet.getRange(targetSheet.getLastRow() + 1, 1);
s.getRange(row, 2, 1, 4).copyTo(target, {contentsOnly:true}); //Only selecting column 2, then the following 4 columns
}
}
Edit*
Example document:
https://docs.google.com/spreadsheets/d/1DFbAp0IN8_UFv9u8kWiZBTxDogj4e7FnuAPzC0grDw0/edit?usp=sharing
Any help greatly appreciated!
Cheers,
Reiel
Since you have a static form the position of the informatin to be copied will not change
Since we know we want to copy over the data we won't need to do any validation of where we are so all of that stuff can go
Sheets have an appendRow method that take care of the bookkeeping involved with finding the last row
This allows us to simplify the script to the following:
function AddClient() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var data = ss.getSheetByName("SETUP").getRange(25, 2, 1, 4).getValues()[0];
ss.getSheetByName("client").appendRow(data);
}
Edit:
To remove duplicates you could do the following:
function AddClient() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var data = ss.getSheetByName("SETUP").getRange(25, 2, 1, 4).getValues()[0];
var clients = ss.getSheetByName("client").getDataRange().getValues();
if (!clients.filter(function(row) {return data.join("|") === row.join("|");})) {
ss.getSheetByName("client").appendRow(data);
}
}
Note that for the particular example there are some problems because the leading zero gets cut off. Sheets is a bit weird and sometimes tries to force the format to be a number even when you set the cells' formats to Text...

Resources