I am trying to hide blank rows in Google Sheet based on edit i.e. change of selection in 'Sheet3!NamedRange1'. I had taken support of these two questions posted in Stakeoverflow community for the same (Hide and Unhide Specific Blank Rows With the Same Button) and (Hide Blank Rows In Google Sheets Based On Selection).
I am using below code to get the desired result. The code hides blank rows but only upto the last row having data i.e. if last row with data is Row No. 80 then after row no. 80 the script will not hide the rows even if the rows are blank. I want to hide all the blank rows.
In addition to above I want to keep specific blank rows unhide based on the row no.
E.g. Donot hide Row No. 12, 15 and 18 even if they are blank.
function onEdit(e) {
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) {
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)
}
}
});
}
}
Any help on above will be appreciated.
I believe your goal is as follows.
You want to hide all empty rows in "Sheet2" when the value of cell "A1" of "Sheet3" is A.
In this case, you want to exclude the specific rows from the hidden rows.
In this case, how about the following modification?
Modified script:
Before you use this script, please show all rows, and test the script. Please set the excluded rows to excludeRows. In this sample, rows 12, 15, and 18 are excluded from the hidden rows.
function onEdit(e) {
const excludeRows = [12, 15, 18]; // Please set excluded row numbers.
// Ref: https://gist.github.com/tanaikech/5a43281964b739ead2b7ae2401400630
const compilingNumbers = ar => {
const { values } = [...new Set(ar.sort((a, b) => a - b))].reduce((o, e, i, a) => {
if (o.temp.length == 0 || (o.temp.length > 0 && e == o.temp[o.temp.length - 1] + 1)) {
o.temp.push(e);
} else {
if (o.temp.length > 0) {
o.values.push({ start: o.temp[0], end: o.temp[o.temp.length - 1] });
}
o.temp = [e];
}
if (i == a.length - 1) {
o.values.push(o.temp.length > 1 ? { start: o.temp[0], end: o.temp[o.temp.length - 1] } : { start: e, end: e });
}
return o;
}, { temp: [], values: [] });
return values;
};
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) {
const sh2 = e.source.getSheetByName("Sheet2");
const vs = sh2.getRange(1, 1, sh2.getMaxRows(), sh2.getMaxColumns()).getDisplayValues();
const rows = vs.reduce((ar, r, i) => {
if (!excludeRows.includes(i + 1) && r.join("") == "") ar.push(i + 1);
return ar;
}, []);
const method = e.value == "A" ? "hideRows" : "showRows";
compilingNumbers(rows).forEach(({ start, end }) => sh2[method](start, end - start + 1));
}
}
In this script, when the value of cell "A1" of "Sheet3" is A, the empty rows in "Sheet2" are hidden by excluding the specific rows.
Related
I am on basics of appscript and learning it progressively with the help of this community. Any help on below will be appreciated.
I am trying to design a script which hides and unhides rows on change of selection and for that I got a solution from question posted at below link.
Google Sheet Hide/Unhide Rows Using Appscrit Unhide
Below is the code given in above link
function onEdit(e) {
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)
}
}
});
}
}
The code is given proper result but I want a bit modification in the same. The unhide command of the code unhides all the rows of the sheet, however I want the code to unhide all the rows except first row of the sheet.
Any help on above will really be appreciated.
From The unhide command of the code unhides all the rows of the sheet, however I want the code to unhide all the rows except first row of the sheet., how about the following modification?
From:
vs.forEach((r, i) => {
if (r.every(e => e == '')) {
if (e.value == "A") {
sh2.hideRows(i + 1);
} else {
sh2.showRows(i + 1)
}
}
});
To:
vs.forEach((r, i) => {
if (r.every(f => f == '')) {
if (e.value == "A") {
sh2.hideRows(i + 1);
} else if (i > 1) {
sh2.showRows(i + 1);
}
}
});
When this is reflected in your script, sh2.showRows(i + 1) is run when e.value != "A" and i > 1. By this, the 1st row is skipped.
And also, in the case of r.every(e => e == ''), e has already been used with the event object. So, I changed it to f.
I am using the below code to hide and unhide rows which I got from the below question posted in Stakeoverflow community.
Hide Blank Rows In Google Sheets Based On Selection
The code runs when I change the selection in "NamedRange1" i.e. Cell No. A1 of Sheet3. If I select A in this section then blank rows in Sheet2 will get hide and if I select B then the rows will get unhide.
The code runs fine and hides unhides the rows but I am getting issue in below case:
I selected A in NamedRange1 so blank rows in Sheet2 will get hide, then I manually hide one row (Say row No.7) in Sheet2 which is having data and now if I select B in NamedRange1 then the script will unhide hidden rows except Row No. 7. I want to update the script so it can unhide all the hidden rows.
For reference am sharing the link of the sheet which is having the sample data as well as the code.
https://docs.google.com/spreadsheets/d/1s0El5dqirom3UMX2tGihYbdZM8ldWku9F_xqQXi31Ws/edit#gid=1228899550
function onEdit(e) {
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)
}
}
});
}
}
Any help on above will be appreciated.
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)
}
}
});
}
}
I have a sheet where I need to limit the number of checkboxes allowed within a range. Like this
H219 to H225 allows only one checkbox to be checked.
H228: H335 allows three checkboxes.
H340:H347 Allows two checkboxes.
This script works when I use it once, but when i add it multiple times and change the range it seems to stop working.
function onEdit(e) {
const sh=e.range.getSheet();
if(sh.getName()=='GOALS') {
const mcpr=1;
const mcpc=2;
const arrayrange='h219:h225';
const arg=sh.getRange(arrayrange);
const avs=arg.getValues();
const ulr=arg.getRow();
const ulc=arg.getColumn();
const lrr=ulr+arg.getHeight()-1;
const lrc=ulc+arg.getWidth()-1;
if(e.range.columnStart<=lrc && e.range.rowStart<=lrr && e.value=="TRUE") {
let rc=avs[e.range.rowStart-ulr].filter(function(e){return e;}).reduce(function(a,v){ if(v){return a+1;} },0);
if(rc>mcpr){e.range.setValue("FALSE");e.source.toast('Sorry maximum checks per row is ' + mcpr);};
let cc=avs.map(function(r,i){return r[e.range.columnStart-ulc];}).filter(function(e){return e}).reduce(function(a,v){if(v){return a+1;}},0);
if(cc>mcpc){e.range.setValue('FALSE');e.source.toast('Sorry maximum checks per column is ' + mcpc);};
}
}
}
//
function onEdit(e) {
const sh=e.range.getSheet();
if(sh.getName()=='GOALS') {
const mcpr=1;
const mcpc=3;
const arrayrange='h236:h244';
const arg=sh.getRange(arrayrange);
const avs=arg.getValues();
const ulr=arg.getRow();
const ulc=arg.getColumn();
const lrr=ulr+arg.getHeight()-1;
const lrc=ulc+arg.getWidth()-1;
if(e.range.columnStart<=lrc && e.range.rowStart<=lrr && e.value=="TRUE") {
let rc=avs[e.range.rowStart-ulr].filter(function(e){return e;}).reduce(function(a,v){ if(v){return a+1;} },0);
if(rc>mcpr){e.range.setValue("FALSE");e.source.toast('Sorry maximum checks per row is ' + mcpr);};
let cc=avs.map(function(r,i){return r[e.range.columnStart-ulc];}).filter(function(e){return e}).reduce(function(a,v){if(v){return a+1;}},0);
if(cc>mcpc){e.range.setValue('FALSE');e.source.toast('Sorry maximum checks per column is ' + mcpc);};
}
}
}
Thank you so much, I have searched far and wide and this was the best script i could find, i just need it to work in about 6 places within the same sheet, with each range allowing a different number of checkboxes.
I believe your current situation and goal as follows.
You have a Google Spreadsheet that the checkboxes are put to the cells H219:H225, H228:H335 and H340:H347.
You want to give the limitation to the number for checking the checkboxes in each range.
For example, H219:H225, H228:H335 and H340:H347 have the limitations of 1, 3 and 2, respectively.
You want to achieve this using Google Apps Script.
In this case, in order to achieve your goal, I would like to propose a sample script using an array including the ranges and limitations. The script is run by the OnEdit simple trigger.
Sample script:
Please copy and paste the following script to the script editor of Google Spreadsheet and set the variables of obj and sheetName, and save it. When you use this script, please check the checkboxes in the ranges H219:H225, H228:H335 and H340:H347. By this, the script is run by the simple trigger of OnEdit.
function onEdit(e) {
// Please set the ranges and limitations.
const obj = [
{range: "H219:H225", limit: 1},
{range: "H228:H335", limit: 3},
{range: "H340:H347", limit: 2},
];
const sheetName = "Sheet1"; // Please set the sheet name of the sheet including the checkboxes.
const range = e.range;
const editedColumn = range.getColumn();
const editedRow = range.getRow();
const sheet = range.getSheet();
if (sheet.getSheetName() != sheetName) return;
obj.forEach(({range}, i) => {
const temp = sheet.getRange(range);
const startRow = temp.getRow();
const startColumn = temp.getColumn();
obj[i].startRow = startRow;
obj[i].endRow = startRow + temp.getNumRows() - 1;
obj[i].startColumn = startColumn;
obj[i].endColumn = startColumn + temp.getNumColumns() - 1;
});
for (let i = 0; i < obj.length; i++) {
if (editedRow >= obj[i].startRow && editedRow <= obj[i].endRow && editedColumn >= obj[i].startColumn && editedColumn <= obj[i].endColumn) {
const n = sheet.getRange(obj[i].range).getValues().filter(([h]) => h === true).length;
if (n == obj[i].limit + 1) {
range.uncheck();
// Browser.msgBox("Number of checked checboxes are over the limitation."); // If you want to open the dialog, you canm use this.
} else if (n > obj[i].limit + 1) {
Browser.msgBox("Checed checkboxes of existing checkboxes have already been over the limitation number of " + obj[i].limit);
}
break;
}
}
}
Result:
When above script is used, the following result is obtained.
Note:
This sample script is run by the OnEdit simple trigger. So when you directly run the script with the script editor, an error occurs. Please be careful this.
References:
Simple Triggers
Event Objects
I wonder if you could do something like this:
You can add a new section for every range trow is top row, brow is bottom row, lcol is left column and rcol is right column and they are arrays
function onEdit(e) {
const sh = e.range.getSheet();
const trow = [236];
const brow = [244];
const lcol = [8];
const rcol = [8];
const mcpr = [1];
const mcpc = [3];
if (sh.getName() == 'GOALS' && e.range.columnStart >= lcol[0] && e.range.columnStart <= rcol[0] && e.range.rowStart >= trow[0] && e.range.rowStart <= brow[0] && e.value == 'TRUE') {
let vs = sh.getRange(trow[0], lcol[0], brow[0] - trow[0] + 1, rcol[0] - lcol[0] + 1).getValues();
let rc = vs[e.range.rowStart - trow[0]].filter(e =>return e).reduce((a, v) => { if (v) return (a + 1); }, 0);
if (rc > mcpr[0]) { e.range.setValue("FALSE"); e.source.toast('Sorry maximum checks per row is ' + mcpr[0]); };
let cc = vs.map((r, i) => { return r[e.range.columnStart - lcol[0]] }).filter(e =>return e;).reduce((a, v) => { if (v) return a + 1; });
if (cc > mcpc[0]) { e.range.setValue('FALSE'); e.source.toast('Sorry maximum checks per column is ' + mcpc[0]) };
if (sh.getName() == 'GOALS' && e.range.columnStart >= lcol[1] && e.range.columnStart <= rcol[1] && e.range.rowStart >= trow[1] && e.range.rowStart <= brow[1] && e.value == 'TRUE') {
let vs = sh.getRange(trow[1], lcol[1], brow[1] - trow[1] + 1, rcol[1] - lcol[1] + 1).getValues();
let rc = vs[e.range.rowStart - trow[1]].filter(e =>return e).reduce((a, v) => { if (v) return (a + 1); }, 0);
if (rc > mcpr[1]) { e.range.setValue("FALSE"); e.source.toast('Sorry maximum checks per row is ' + mcpr[1]); };
let cc = vs.map((r, i) => { return r[e.range.columnStart - lcol[1]] }).filter(e =>return e;).reduce((a, v) => { if (v) return a + 1; });
if (cc > mcpc[1]) { e.range.setValue('FALSE'); e.source.toast('Sorry maximum checks per column is ' + mcpc[1]) };
}
}
}
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.