Upgrading a script - google-apps-script

So i have this script
function onEdit(e) {
var editRange = e.range;
var rb1 = SpreadsheetApp.getActiveSpreadsheet().getRangeByName("RadioButton1");
// return if out of range
if (editRange.getSheet().getSheetName() != rb1.getSheet().getSheetName()) return;
var radioRange = { // RadioButton1 range
top : rb1.getRow(),
bottom : rb1.getLastRow(),
left : rb1.getColumn(),
right : rb1.getLastColumn()
};
var thisRow = editRange.getRow();
if (thisRow < radioRange.top || thisRow > radioRange.bottom) return;
var thisCol = editRange.getColumn();
if (thisCol < radioRange.left || thisCol > radioRange.right) return;
// make RadioButton1 named range behave like radio button
var numRows = rb1.getNumRows();
var numCols = rb1.getNumColumns();
var writeValues = []
for (var i = 1; i <= numRows; i++) {
var row = []
for (var j = 1; j <= numCols; j++) {
var currentRange = rb1.getCell(i,j);
var radioValue;
if ( currentRange.getA1Notation() == editRange.getA1Notation() ) {
radioValue = true;
} else {
radioValue = false;
}
row.push(radioValue)
}
writeValues.push(row)
}
rb1.setValues(writeValues)
}
from Switch based on checkbox tied between two cells in Google Sheets
so sorry i for got i have put the checkboxes into separate ranges
What ranges i Have
how do i make this now work for multiple ranges please?
also if there is a way to set the cells the check boxes are in rather than using a range to determine the checkbox pairs so i can move them around the sheet and tell the script where to look that would be better
My Google Doc

Description
The following example script shows how to toggle checkboxes like radio button in which only one checkbox of two remains checked. The checkboxes are grouped by rows as shown.
Code.gs
function onEdit(e) {
let rButton = SpreadsheetApp.getActiveSpreadsheet().getRangeByName("RadioButtons"); // A1:B5
let rCheck = null;
if( e.range.getSheet().getName() === rButton.getSheet().getName() ) {
if( e.range.getRow() < rButton.getRow() || e.range.getRow() > rButton.getLastRow() ) return;
if( e.range.getColumn() < rButton.getColumn() || e.range.getColumn() > rButton.getLastColumn() ) return;
// unchecked does not become checked, only check becomes unchecked
let offset = e.range.getColumn() === 1 ? 1 : -1;
e.range.offset(0,offset,1,1).uncheck();
}
}
Reference
Range.offset()
Range.uncheck()

Related

onEdit(e) less than 30s trigger problem require help to shorten code to make it more efficient

I have a problem where my trigger onEdit() stopped after 30seconds and i need to keep re-pasting to trigger it again. Can you take a look and guide me on how to shorten it to make it work? I saw a person writing about it on this forum link: Google sheets script, timed out just after 30ish something seconds but i was not able to relate to it. Can anybody help me? Much appreciated as i am very new to this.
My code is as per below:
function onEdit(e){
//Variables
var row = e.range.getRow();
var col = e.range.getColumn();
var IBDataEntry = "IB";
var IBDataSheet = e.source.getSheetByName(IBDataEntry);
var IBlastRow = IBDataSheet.getLastRow();
var InboundPO = "Inbound POs";
var InboundPOSheet = e.source.getSheetByName(InboundPO);
var InboundlastRow = InboundPOSheet.getLastRow();
var searchRange = InboundPOSheet.getRange(2,2,InboundlastRow,1);
var rangeValues = searchRange.getValues();
var IBDataSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("IB");
//Data Entry for Date Of Approval
if(col === 22 && row > 2 && e.source.getActiveSheet().getName() === IBDataEntry){
var POList3 = IBDataSheet.getRange("V3:V").getValues();
var LastRow3 = POList3.filter(String).length + 2;
var Entries3 = LastRow3 - row + 1;
for (var x = 0; x < Entries3 ; x++){
var ApprovalPO = IBDataSheet.getRange(row + x, 22).getValue();
var ApprovalDate = IBDataSheet.getRange(row + x, 23).getValue();
if (IBDataSheet.getRange(row + x, 23).getValue() != '')
for(var j=1;j<=InboundlastRow;j++){
if(rangeValues[j] == ApprovalPO){
InboundPOSheet.getRange(j+2,11).setValue(ApprovalDate);
}
}
}
} SummarySheet.getRange(36,3).setValue(currentDate);
//Data Entry for Date Of Arranging
if(col === 22 && row > 2 && e.source.getActiveSheet().getName() === IBDataEntry){
var POList4 = IBDataSheet.getRange("V3:V").getValues();
var LastRow4 = POList4.filter(String).length + 2;
var Entries4 = LastRow4 - row + 1;
for (var x = 0; x < Entries4 ; x++){
var ArrangingPO = IBDataSheet.getRange(row + x, 22).getValue();
var ArrangingDate = IBDataSheet.getRange(row + x, 24).getValue();
if (IBDataSheet.getRange(row + x, 24).getValue() != '')
for(var j=1;j<=InboundlastRow;j++){
if(rangeValues[j] == ArrangingPO){
InboundPOSheet.getRange(j+2,12).setValue(ArrangingDate);
}
}
}
} SummarySheet.getRange(36,3).setValue(currentDate);
}
Thank you so much!

The most optimal way to invert checkboxes in Google Script?

Because Google Script does not support radio buttons, I tried to create a workaround. Though I have learned the basics of Python, I'm new to Javascript/Google Sctipt. I finally got my code to work but I feel the result is far too clumsy for such a simple task. How to optimize it?
Here is the working sample: https://docs.google.com/spreadsheets/d/1bcMj3Yxewo4ZUgnhg0z46NyqJYBfxm-6ocvmEHLwtWE/edit?usp=sharing
And here's my code:
const ss = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
function onEdit(e) {
var invertedRange = { // C4:D5 is the range to follow
top : 4,
bottom : 5,
left : 3,
right : 4
}
var thisRow = e.range.getRow();
var thisCol = e.range.getColumn();
// Invert the checkboxes in the range
if (thisRow <= invertedRange.bottom && thisRow >= invertedRange.top) {
if (thisCol <= invertedRange.right && thisCol >= invertedRange.left) {
var changeArray = ss.getRange(invertedRange.top, invertedRange.left, 2, 2).getValues();
var invertedArray = [];
var rPos = 0; // first row of the 2x2 matrix
var valueToAdd = true;
for (var readRow = invertedRange.top; readRow <= invertedRange.bottom; readRow = readRow + 1) {
var cPos = 0; // first column of the 2x2 matrix
var invertedPart = [];
for (var readCol = invertedRange.left; readCol <= invertedRange.right; readCol = readCol + 1) {
if (thisRow == readRow && thisCol == readCol) {
var valueToAdd = changeArray[rPos][cPos]; // do not invert the checkbox that was already manually changed by user edit
} else {
var valueToAdd = !changeArray[rPos][cPos]; // invert all other checkboxes in the range
}
var invertedPart = invertedPart.concat(valueToAdd); // create an array from a pair of checkbox values
cPos = cPos + 1;
}
invertedArray[rPos]=invertedPart; // add the pairs into an array
rPos = rPos + 1;
}
ss.getRange(invertedRange.top, invertedRange.left, 2, 2).setValues(invertedArray); // set the chackbox values
} return;
}
}
Give Checkboxes Radio Group Behavior
This onEdit function provide radio button group behavior for any range. You are required to enter the range in A1 Notation by setting the value of cbrg and selecting which direction you want the groups to be in either row or col. It will turn all the other cells in the row or column that you select by clicking it to true. It doesn't support validation values other than true or false.
function onEdit(e) {
//e.source.toast('Entry');
const sh = e.range.getSheet();
const group = 'col';//change which direction that you wish to group in
const cbrg = "C4:D5";//specifying the range the starting column and row must be greater than one.
const rg = sh.getRange(cbrg);
const w = rg.getWidth();
const h = rg.getHeight();
const cs = rg.getColumn();
const ce = cs + w - 1;
const rs = rg.getRow();
const re = rs + h - 1;
//console.log('rs:%s,re:%s,cs:%s,ce:%s', rs, re, cs, ce);
if (sh.getName() == 'Sheet1' && e.range.columnStart > cs - 1 && e.range.columnStart < ce + 1 && e.range.rowStart > rs - 1 && e.range.rowStart < re + 1 && e.value == 'TRUE') {
if (group == 'row') {
//e.source.toast('row');
let cA = new Array(w).fill(false);
cA[e.range.columnStart - cs] = true;
sh.getRange(e.range.rowStart, cs, 1, cA.length).setValues([cA]);
}
if (group == 'col') {
//e.source.toast('col');
let idx = e.range.rowStart - rs;
let cA = [];
for (let i = 0; i < h; i++) {
if (i == idx) cA.push([true]);
else cA.push([false]);
}
sh.getRange(rs, e.range.columnStart, cA.length, 1).setValues(cA);
}
}
}
The one below performs a little faster and is fairly easy to just set the top row (trow), bottom row (brow), left column (lcol), right col (rcol) and the radio group direction 'row' or 'col'. It requires quite few less function calls which are time consuming.
function onEdit(e) {
//e.source.toast('Entry');
const sh = e.range.getSheet();
const trow=4;//top row
const brow=5;//bottom row
const lcol=3;//left column
const rcol=4;//right column
const group = 'row';//change which direction that you wish to group in
//console.log('trow:%s,brow:%s,lcol:%s,rcol:%s', trow, brow, lcol, rcol);
if (sh.getName() == 'Sheet1' && e.range.columnStart >= lcol && e.range.columnStart <= rcol && e.range.rowStart >= trow && e.range.rowStart <= brow && e.value == 'TRUE') {
if (group == 'row') {
//e.source.toast('row');
let cA = new Array(rcol-lcol+1).fill(false);
cA[e.range.columnStart - lcol] = true;
sh.getRange(e.range.rowStart, lcol, 1, cA.length).setValues([cA]);
}
if (group == 'col') {
//e.source.toast('col');
let idx = e.range.rowStart - trow;
let cA = [];
for (let i = 0; i < brow-trow+1; i++) {
if (i == idx) cA.push([true]);
else cA.push([false]);
}
sh.getRange(trow, e.range.columnStart, cA.length, 1).setValues(cA);
}
}
}
Animation:
I changed the range on the animation so I could fit it into the smallest image possible.

Google Scripts - Compare Color & Change Font Color

I am trying to compare data from two different columns and when they don't match I want the data in column 6 to turn Red and if it is changed back to match it turns back to black.
I don't want to use to conditional formating from the menue because I dont want people to be able to turn it off.
function onEdit(e) {
var sheet = e.source.getActiveSheet();
var activeCell = sheet.getActiveCell();
var col = activeCell.getColumn();
var row = activeCell.getRow();
var col6 = activeCell.getColumn( col == 6 && row >=5 && row <=55 && sheet);
var col14 = activeCell.getColumn( col == 14 && row >=5 && row <=55 && sheet);
var first = get.Value(col6);
var second = get.Value(col14);
//Start Time Function
if (col == 1 && row>=5 && row<=55 && sheet.getRange(row,col).getValue() == ""){
sheet.getRange(row,1,1,sheet.getMaxColumns()).clearContent();
}
else if (col == 1){
sheet.getRange(row,col+11).setValue(new Date()).setNumberFormat('hh:mm:ss');
sheet.getRange(row,col+13).setValue(new Date()).setNumberFormat('hhmm');
sheet.getRange(row,col+5).setValue(new Date()).setNumberFormat('hhmm');
sheet.getRange(row,col+3).setValue(new Date()).setNumberFormat();
}
//End Time Function
if (col == 8 && row>=5 && row<=55 && sheet.getRange(row,col).getValue() == ""){
sheet.getRange(row,7,1).clearContent() && sheet.getRange(row,13,1).clearContent() && sheet.getRange(row,15,1).clearContent();
}
else if (col == 8) {
sheet.getRange(row,col+5).setValue(new Date()).setNumberFormat('hh:mm:ss');
sheet.getRange(row,col+7).setValue(new Date()).setNumberFormat('hhmm');
sheet.getRange(row,col-1).setValue(new Date()).setNumberFormat('hhmm');
}
// Data Valadation Start Time
// Confirm data in column 6 matches column 14, if different set column 6 font to red.
if(first != second){
var cell = sheet.getRange(first);
cell.setFontColor("red");
}
else if(first == second){
var cell = sheet.sheet.getRange(first);
cell.setFontColor("black");
}
}
Working Code.
function onEdit(e) {
var sheet = e.source.getActiveSheet();
var activeCell = sheet.getActiveCell();
var col = activeCell.getColumn();
var row = activeCell.getRow();
//Start Time Function
if (col == 1 && row>=5 && row<=55 && sheet.getRange(row,col).getValue() == ""){
sheet.getRange(row,1,1,sheet.getMaxColumns()).clearContent();
}
else if (col == 1){
sheet.getRange(row,12,1).setValue(new Date()).setNumberFormat('hh:mm:ss'); //real time
sheet.getRange(row,14,1).setValue(new Date()).setNumberFormat('hhmm').setNumberFormat("#");
sheet.getRange(row,6,1).setValue(new Date()).setNumberFormat('hhmm').setNumberFormat("#");
sheet.getRange(row,4,1).setValue(new Date()).setNumberFormat(); //Date Row
} // END - Start Time Function
//End Time Function
if (col == 8 && row>=5 && row<=55 && sheet.getRange(row,col).getValue() == ""){
sheet.getRange(row,7,1).clearContent() && sheet.getRange(row,13,1).clearContent() && sheet.getRange(row,15,1).clearContent();
}
else if (col == 8) {
sheet.getRange(row,13,1).setValue(new Date()).setNumberFormat('hh:mm:ss'); // real time
sheet.getRange(row,15,1).setValue(new Date()).setNumberFormat('hhmm').setNumberFormat("#");
sheet.getRange(row,7,1).setValue(new Date()).setNumberFormat('hhmm').setNumberFormat("#");
} // END - End Time Function
// Data Valadation - Start Time
var firstF = sheet.getRange(row,6,1);
var secondN = sheet.getRange(row,14,1);
if(firstF.getValue() != secondN.getValue()){
var cell = firstF;
cell.setFontColor("red");
}
else {
var cell = firstF;
cell.setFontColor("black");
} // END - Data Valadation - Start Time
// Data Valadation - End Time
var firstG = sheet.getRange(row,7,1);
var secondO = sheet.getRange(row,15,1);
if(firstG.getValue() != secondO.getValue()){
var cell = firstG;
cell.setFontColor("red");
}
else {
var cell = firstG;
cell.setFontColor("black");
} // END - Data Valadation - End Time
} // END - function onEdit
Be careful not to mix up ranges and values
Also, you seem to use methods methods that do not exist in Apps Script / cannot be used with parameters
Please check the documentation for getValues() and getColumn()
A modified working sample based on your code:
function onEdit(e) {
var activeCell = e.range;
var sheet = e.range.getSheet();
var col = activeCell.getColumn();
var row = activeCell.getRow();
var first = sheet.getRange(row, 6);
var second = sheet.getRange(row, 14);
if(first.getValue() != second.getValue()){
var cell = first;
cell.setFontColor("red");
}
else {
var cell = first;
cell.setFontColor("black");
}
}

Trying to update dynamic dropdown code to support two columns dependent on the same column

I am trying to create a tracker that has two dropdown columns that are dependent on the same column. For example, I am providing 3 different services to clients, and each service has it's own type of support and outcome. When one of the services is selected I would like the support and outcome columns to populate with the items that are relevant to that service.
Currently some of the outcome options will populate, but not all of them.
I found and am trying to manipulate code used in this post, How do you do dynamic / dependent drop downs in Google Sheets?.
I created a second section to get the dropdowns for the outcomes. Here's a sample document of what I've been able to accomplish, https://docs.google.com/spreadsheets/d/1KmET4ilVqxGQwnIKGGo2hPFxWcQCtrXta-z8OHJT_5c/edit?usp=sharing
Thank you!
function onEdit() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = SpreadsheetApp.getActiveSheet();
var myRange = SpreadsheetApp.getActiveRange();
var dvSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Dropdowns");
var option = new Array();
var startCol = 0;
// Dynamic dropown for Support Provided
if(sheet.getName() == "Tracker" && myRange.getColumn() == 3 && myRange.getRow() > 1){
if(myRange.getValue() == "Credit"){
startCol = 3;
} else if(myRange.getValue() == "Legal"){
startCol = 4;
} else if(myRange.getValue() == "Housing"){
startCol = 5;
} else {
startCol = 6
}
if(startCol > 0 && startCol < 6){
option = dvSheet.getSheetValues(3,startCol,6,1);
var dv = SpreadsheetApp.newDataValidation();
dv.setAllowInvalid(false);
//dv.setHelpText("Some help text here");
dv.requireValueInList(option, true);
sheet.getRange(myRange.getRow(),myRange.getColumn() + 1).setDataValidation(dv.build());
}
if(startCol == 6){
sheet.getRange(myRange.getRow(),myRange.getColumn() + 1).clearDataValidations();
}
}
// Dynamic dropdown for Outcome
if(sheet.getName() == "Tracker" && myRange.getColumn() == 3 && myRange.getRow() > 1){
if(myRange.getValue() == "Credit"){
startCol = 7;
} else if(myRange.getValue() == "Legal"){
startCol = 8;
} else if(myRange.getValue() == "Housing"){
startCol = 9;
} else {
startCol = 10
}
if(startCol > 6 && startCol < 10){
option = dvSheet.getSheetValues(6,startCol,10,1);
var dv = SpreadsheetApp.newDataValidation();
dv.setAllowInvalid(false);
//dv.setHelpText("Some help text here");
dv.requireValueInList(option, true);
sheet.getRange(myRange.getRow(),myRange.getColumn() + 2).setDataValidation(dv.build());
}
if(startCol == 10){
sheet.getRange(myRange.getRow(),myRange.getColumn() + 2).clearDataValidations();
}
}
}
You have specified incorrect start row (i.e. "6") for the option list of the Outcome dropdown (code line 48):
option = dvSheet.getSheetValues(6,startCol,10,1);
Replace "6" by "3" and you will have correct options list in the dropdown.

Hide Rows onEdit in Google Sheets

I need a script triggered by a yes/no field in B2 to hide all rows in Budget_01 sheet wherever 0 is found in column B. For example in table Budget_01:
BUDGET_01 TABLE WITH HIDE ZERO [B2]= "No" All rows unhidden
Budget_01
Hide Zero|No
Expense | Amount | Frequency
Car loan 500.00 1
Gas 0 0
Music 0 1
Supplies 50 20
....
Books 0 0
BUDGET_01 TABLE WITH HIDE ZERO [B2]= "Yes" All rows where column B = $0 hidden
Budget_01
Hide Zero|Yes
Expense | Amount | Frequency
Car loan 500.00 1
Supplies 50 20
This is my attempt that does not work:
function toggleRows() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("Budget 01");
var cellValue = sheet.getRange("B2").getValue();
var sheets = ss.getSheets();
if(cellValue == 'No'){
for(var i = 0, iLen = sheets.length; i < iLen; i++) {
// get sheet
var sh = sheets[i];
// unhide rows
var rRows = sh.getRange("A1:A");
sh.unhideRow(rRows);
}
}
if(cellValue == 'Yes'){
ss.showRows(1, ss.getMaxRows());
ss.getRange('B1:B')
.getValues()
.forEach(function (r, i) {
if (r[0] !== '' && r[0].toString()
.charAt(0) == 0) ss.hideRows(i + 1)
});
}
}
Rather than use the yes/no drop down to launch the script I inserted two graphic buttons (insert > drawing square button) marked with yes and no. I launched these two functions from each button:
function hide01Rows() {
// set up spreadsheet and sheet
var ss = SpreadsheetApp.getActiveSpreadsheet(), sheets = ss.getSheets();
for(var i = 0, iLen = sheets.length; i < iLen; i++) {
// get sheet
var sh = sheets[i];
sh.showRows(1, sh.getMaxRows());
sh.getRange('B:B')
.getValues()
.forEach(function (r, i) {
if (r[0] !== '' && r[0].toString()
.charAt(0) == 0) sh.hideRows(i + 1)
});
}
}
function unhide0Rows() {
// set up spreadsheet and sheet
var ss = SpreadsheetApp.getActiveSpreadsheet(), sheets = ss.getSheets();
for(var i = 0, iLen = sheets.length; i < iLen; i++) {
// get sheet
var sh = sheets[i];
// unhide rows
var rRows = sh.getRange("A:A");
sh.unhideRow(rRows);
}
}
This is not the most graceful solution but it does what I need.
Please change the function name of toggleRows() to onEdit(), and change B1 of "No" to "Yes". By this, when spreadsheet was edited, Budget_01--Unhidden becomes Budget_01--Hidden. If my interpretation was wrong, please tell me it.
Rather than using forEach, I used an else if statement to loop through the Yes condition.
function onEdit() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("Sheet1"); // Change for your sheet
// Check the condition
var cellValue = sheet.getRange("B1").getValue();
// To hide/unhide, you need a range, not the value. Split into to variables.
var range = sheet.getDataRange();
var values = range.getValues();
// Check the data being returned
Logger.log(cellValue);
Logger.log(values);
// If "No," unhide the entire range.
if(cellValue == "No" || "no") {
for(var i = 0; i < values.length; i++) {
Logger.log("Unhide all rows.");
sheet.unhideRow(range);
}
// If "Yes," loop through the data looking for 0's and hide the range.
} else if (cellValue == "Yes" || "yes") {
for(var i=0; i<values.length;i++) {
if(values[i][1] == 0) {
Logger.log("Found 0 at row " + i);
sheet.hideRow(sheet.getRange(i+1,1));
}
}
}
}