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.
Related
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 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.
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 script that populates with today's date column J when column A is filled.
function Populate() {
var sheetNameToWatch = "MASTER";
var columnNumberToWatch = /* column A */ 1;
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = SpreadsheetApp.getActiveSheet();
var range = sheet.getActiveCell();
var val=sheet.getActiveCell().getValue()
if (sheet.getName() == sheetNameToWatch && range.getColumn() == columnNumberToWatch && val!= "" ) {
var targetCell = sheet.getRange(range.getRow(), range.getColumn()+9
);
targetCell.setValue("" + Utilities.formatDate(new Date(), "GMT", "yyyy-MM-dd"));
}
}
I think it's quite slow and I also would like to fill more columns at once, aside from the date on Column J:
Column I: "No payment"
Column L: "PENDING"
In order to fill multiple columns and try to make it work faster, I've also tested another version:
function Populate2(e)
{
var sheet = e.source.getActiveSheet();
if (sheet.getName() !== 'MASTER'|| e.range.getColumn() !== 1)
{
return;
}
for(var i=0;i<e.range.getNumRows();i++)
{
var offset=e.range.getRow() + i;
sheet.getRange('I'+ offset).setValue("No Payment");
sheet.getRange('J'+ offset).setValue(new Date());
sheet.getRange('L'+ offset).setValue("PENDING");
}
}
The last version has the problem that even if I clean the column A, the values are filled.
Couldn't figure out which version - if any - would be the best approach to improve regarding efficiency, and how.
Can anyone give me a hand?
Thank you in advance.
I believe your goal is as follows.
You want to reduce the process cost of your script.
When the values of column "A" are removed, you want to clear the columns "I", "J", and "L".
In this case, how about the following modification?
Modified script:
function Populate2(e) {
var range = e.range;
var sheet = e.source.getActiveSheet();
if (sheet.getSheetName() !== 'MASTER' || range.columnStart !== 1) {
return;
}
var values = range.getDisplayValues();
var { noPayment, date, pending, clear } = values.reduce((o, [a], i) => {
var row = range.rowStart + i;
if (a == "") {
o.clear.push(...["I", "J", "L"].map(e => e + row));
} else {
o.noPayment.push("I" + row);
o.date.push("J" + row);
o.pending.push("L" + row);
}
return o;
}, { noPayment: [], date: [], pending: [], clear: [] });
if (noPayment.length > 0) {
sheet.getRangeList(noPayment).setValue("No Payment");
sheet.getRangeList(date).setValue(new Date());
sheet.getRangeList(pending).setValue("PENDING");
}
if (clear.length > 0) {
sheet.getRangeList(clear).clearContent();
}
}
In this modification, the values are put to the cells using the range list. And also, the cells are cleared using the range list.
Note:
From your question, this modified script supposes that your function Populate2 is installed as OnEdit trigger. Please be careful about this.
I think that in your script, onEdit simple trigger might be also used. But, I'm not sure about your actual situation. So I used Populate2 in your script.
References:
getRangeList(a1Notations)
Class RangeList
Try this:
function Populate2(e) {
//e.source.toast('Entry');
var sh = e.range.getSheet();
if (sh.getName() !== 'Master' || e.range.columnStart !== 1) { return; }
//e.source.toast('flag1')
let n = e.range.rowEnd - e.range.rowStart + 1;
let dt = new Date();
let i = [...Array.from(new Array(n).keys(),x => ["No Payment"])];
let j = [...Array.from(new Array(n).keys(),x => [dt])];
let l = [...Array.from(new Array(n).keys(),x => ["PENDING"])];
sh.getRange(e.range.rowStart, 9, n).setValues(i);
sh.getRange(e.range.rowStart, 10, n).setValues(j);
sh.getRange(e.range.rowStart, 12, n).setValues(l);
}
or
function Populate2(e) {
//e.source.toast('Entry');
var sh = e.range.getSheet();
if (sh.getName() !== 'Master' || e.range.columnStart !== 1) { return; }
//e.source.toast('flag1')
let n = e.range.rowEnd - e.range.rowStart + 1;
let dt = new Date();
let i = [...Array.from(new Array(n).keys(),x => ["No Payment",dt])];
let l = [...Array.from(new Array(n).keys(),x => ["PENDING"])];
sh.getRange(e.range.rowStart, 9, n, 2).setValues(i);
sh.getRange(e.range.rowStart, 12, n).setValues(l);
}
Array.from()
Array Constructor
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]) };
}
}
}