The most optimal way to invert checkboxes in Google Script? - google-apps-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.

Related

Upgrading a 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()

Hide Blank Rows In Google Sheets Based On Selection

I have designed below script from solution given in one of the question posted from the Stakeoverflow community (Show and hide rows and columns based on different dropdowns).
In the solution given in the above link, specific rows/columns are being hide by using the script, I want to hide all the blank rows in place of specific rows.
Script which I am using
function onEdit(e) {
const n1=e.source.getRangeByName('Sheet3!NamedRange1').getValue();
const sh2=e.source.getSheetByName("Sheet2");
if (n1=="A"){sh2.showRows(15,10);}else{sh2.hideRows(15,10);}
}
Any help on above will be greatly appreciated.
Hide and Show Row based on value in NamedRang1
function onEdit(e) {
//e.source.toast("Entry")
const sh = e.range.getSheet();
const rg = e.source.getRangeByName("NamedRange1");
const sr = rg.getRow();
const sc = rg.getColumn();
const er = sr + rg.getHeight() - 1;
const ec = sc + rg.getWidth() - 1;
if (sh.getName() == "Sheet3" && e.range.columnStart >= sc && e.range.columnStart <= ec && e.range.rowStart >= sr && e.range.rowStart <= er && e.value) {
//e.source.toast("Flag1")
const sh2 = e.source.getSheetByName("Sheet2");
const vs = sh2.getDataRange().getValues();
vs.forEach((r, i) => {
if (r.every(e => e == '')) {
if (e.value == "A") {
sh2.hideRows(i + 1);
} else {
sh2.showRows(i + 1)
}
}
});
}
}

Toggle rows hide/show between top down checkbox

trying to make a semi/manual tree structure. if check box = true. in a topdown manner. it will look for another check box in the downside direction and unhide rows. i.e. show A2 to A9. if box = false then, Hide: A11 till A19. if there is no check box until the last row it will hide/show all the rows. i.e. B23/B2000. it's a top-down approach so the only action only is limited to a particular column and between the boxes cells are blanks.
function onEdit(e){
const sheet = SpreadsheetApp.getActiveSheet();
const range = e.range;
const editedValue = e.value;
if (range.getA1Notation() === "A1" && editedValue === "TRUE") {
sheet.unhideRow(sheet.getRange("A:A9"));
} else if (range.getA1Notation() === "A1" && editedValue === "TRUE") {
const firstRow = 1;
const lastRow = sheet.getLastRow();
if (pValue === true) {
sheet.hideRows(i + firstRow);
}
});
} else if (e.range.columnStart === 9 && editedValue === "TRUE") {
sheet.hideRows(range.rowStart);
}
}
Solution:
You can use this script to check the rows below the edited checkbox and hide/show rows accordingly:
function onEdit(e){
const sheet = SpreadsheetApp.getActiveSheet();
const range = e.range;
const editedValue = e.value;
const row = range.getRow();
const col = range.getColumn();
const lastRow = sheet.getMaxRows();
var data = sheet.getRange(row+1,col,lastRow-row+1,1).getValues();
for (var i = 0; i < data.length; i++) {
if (data[i][0] === true || data[i][0] === false) {
++i;
break;
}
}
if (i > 1) {
if (editedValue === "FALSE")
sheet.hideRows(row+1,i-1);
else if (editedValue === "TRUE")
sheet.showRows(row+1,i-1);
}
}
Sample Output:

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!

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.