I have written the script below to hid all rows that have a specific box checked or if a different cell has the word "Newer". The problem I am running into is that when I run the hideReviewed_ function it undoes the hidden rows done by the second function hideNewer_. How do I blend these 2 so that I can run 1 function and it will look and both and hid the both the checked boxes and the items that say "Newer"?
function onOpen() {
var spreadsheet = SpreadsheetApp.getActive();
var menuItems = [
{name: 'Reviewed', functionName: 'hideReviewed_'},
{name: 'Newer', functionName: 'hideNewer_'}
];
spreadsheet.addMenu('Hiding Time', menuItems);
}
function hideReviewed_() {
var s = SpreadsheetApp.getActive().getSheetByName('2 Week Snapshot');
s.showRows(1, s.getMaxRows());
s.getRange('C:C')
.getValues()
.forEach( function (r, i) {
if (r[0] == 1)
s.hideRows(i + 1);
});
}
function hideNewer_(e) {
var s = SpreadsheetApp.getActive().getSheetByName('2 Week Snapshot');
s.showRows(1, s.getMaxRows());
s.getRange('J:J')
.getValues()
.forEach( function (r, i) {
if (r[0] == 'Newer')
s.hideRows(i + 1);
});
}
Here you go:
function hideReviewed() {
var s = SpreadsheetApp.getActive().getSheetByName('Sheet1');
s.showRows(1, s.getMaxRows());
s.getRange('C:J').getValues().forEach(function (r, i) {if (r[0] == 1 || r[7] == "Newer") s.hideRows(i + 1)});
}
Changes that were made:
I get the entire range C:J
In the if statement I check to see if r[0] is equal to 1 OR if r[7] is equal to "Newer".
r[0] is Column C
r[7] is Column J
Tested this in a sample spreadsheet and seems to be working as intended.
Hope this helps.
Before:
After:
Related
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 to use the sheet a lot on phone,
but it's quite awkward because it often doesn't show the delete button right away and I have to select it after the three dots
to make this easier, i thought i would add the word delete to the data
if I have already written something into the cell then selecting delete will delete the contents of the cell
the tricky part is that if something is already in the cell and I select Delete then it will write it afterwards so e.g.
write "something" > choose "Delete" than got
something, Delete
so i want a match that finds the word delete anywhere in the cell
my thought is that there should be a if( .match(/^Delete/) and clearContent(); somewhere for the newvalue and the oldvalue part but i couldn't figure out how to keep the script working like it used to and even find delete cell too
unchanged script:
function onEdit(e) {
const magicCells = ['B3:U27', 'W3:AP27'];
const ss = SpreadsheetApp.getActive();
let intersect;
if (e.value === undefined
|| !e.oldValue
|| !magicCells.some(rangeA1 => (intersect = getRangeIntersection_(e.range,
ss.getRange(rangeA1))))) {
return;
}
var oldValue;
var newValue;
var activeCell = ss.getActiveCell();
if (activeCell.getColumn() > 1) {
newValue = e.value;
oldValue = e.oldValue;
if (!e.value) {
activeCell.setValue("");
} else {
if (!e.oldValue) {
activeCell.setValue(newValue);
} else {
if (newValue.indexOf(oldValue) < 0) {
activeCell.setValue(oldValue + ', ' + newValue);
} else {
activeCell.setValue(newValue);
};
};
};
};
}
function getRangeIntersection_(range, intersectingRange) {
// version 1.4, written by --Hyde, 19 October 2020
var sheet = range.getSheet();
if (sheet.getSheetId() !== intersectingRange.getSheet().getSheetId()) {
return null;
}
var rowStart = Math.max(range.getRow(), intersectingRange.getRow());
var rowEnd = Math.min(range.getLastRow(), intersectingRange.getLastRow());
if (rowStart > rowEnd) {
return null;
}
var columnStart = Math.max(range.getColumn(), intersectingRange.getColumn());
var columnEnd = Math.min(range.getLastColumn(), intersectingRange.getLastColumn());
if (columnStart > columnEnd) {
return null;
}
return {
sheet: sheet,
range: sheet.getRange(rowStart, columnStart, rowEnd - rowStart + 1, columnEnd - columnStart + 1),
rowStart: rowStart,
columnStart: columnStart,
rowEnd: rowEnd,
columnEnd: columnEnd,
numRows: rowEnd - rowStart + 1,
numColumns: columnEnd - columnStart + 1,
};
}
thanks in advance for your help!
You can refer to this minimum reproducible sample code:
function onEdit(e){
var ss = e.source;
var cell = e.range;
var row = cell.getRow();
var col = cell.getColumn();
Logger.log(row);
Logger.log(col);
Logger.log(cell.getDisplayValue());
//Check if modified cell is in sheet "dropdown menu delete cell with script - unsolved"
//Check if modified cell is within range B3:I27
if(ss.getActiveSheet().getName() == "dropdown menu delete cell with script - unsolved"
&& row >= 3 && row <= 27 && col >= 2 && col <= 9){
if(cell.getDisplayValue().includes('DeleteCell')){
cell.clearContent();
}
}
}
I did not check your existing getRangeIntersection_() to check if the modified cell is within the set of ranges you provided.
I manually checked the row and column index based on your sample sheet
Use String.includes() to check if a string exist within a string (this is in case sensitive)
Clear the content of the cell if it has "DeleteCell" in its value
(UPDATE)
Here is a sample script combined in your existing script (where new data can be appended to the old data if not "DeleteCell" value was selected):
function onEdit(e) {
const magicCells = ['B3:U27', 'W3:AP27'];
const ss = SpreadsheetApp.getActive();
if (e.value === undefined
|| !e.oldValue
|| !magicCells.some(rangeA1 => (intersect = getRangeIntersection_(e.range, ss.getRange(rangeA1))))) {
return;
}
var oldValue;
var newValue;
var activeCell = ss.getActiveCell();
if (activeCell.getColumn() > 1) {
newValue = e.value;
oldValue = e.oldValue;
if (!e.value) {
activeCell.setValue("");
} else {
if (!e.oldValue) {
activeCell.setValue(newValue);
} else {
if (newValue.indexOf(oldValue) < 0) {
if(newValue == 'DeleteCell'){
activeCell.clearContent();
}else{
activeCell.setValue(oldValue + ', ' + newValue);
}
} else {
activeCell.setValue(newValue);
};
};
};
};
}
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]) };
}
}
}
Novice at the google scripting ... so apologies ..
I have the below code that works ... however I have a further ten tabs that this needs to apply to ... is there a way of writing this so you don't have to reference each active sheet?
the idea is to hide rows and columns automatically if a certain value exists in them ...
function onOpen()
{
var s = SpreadsheetApp.getActive().getSheetByName('O4');
s.showRows(1, s.getMaxRows());
s.getRange('BQ:BQ')
.getValues()
.forEach( function (r, i) {
if (r[0] == 'Done')
s.hideRows(i + 1);
});
var b = SpreadsheetApp.getActive().getSheetByName('O4');
b.showColumns(1, b.getMaxColumns());
b.getRange('135:135')
.getValues()[0]
.forEach(function (r, i) {
if (r && r == 'N') b.hideColumns(i + 1)
});
var s = SpreadsheetApp.getActive().getSheetByName('OG3');
s.showRows(1, s.getMaxRows());
s.getRange('BQ:BQ')
.getValues()
.forEach( function (r, i) {
if (r[0] == 'Done')
s.hideRows(i + 1);
});
var b = SpreadsheetApp.getActive().getSheetByName('OG3');
b.showColumns(1, b.getMaxColumns());
b.getRange('135:135')
.getValues()[0]
.forEach(function (r, i) {
if (r && r == 'N') b.hideColumns(i + 1)
});
}
You can just loop over all of the tabs in your sheet using this snippet.
var sheets = SpreadsheetApp.getActiveSpreadsheet().getSheets();
sheets.forEach(function (sheet) {
callYourFunction(sheet)
})
If you need to on apply your code on particular sheets do this.
var sheets = ['SheetA', 'SheetB', 'SheetG', 'SheetH', 'SheetM']
for (var i = 0; i < sheets.length; i++) {
var sheetName = sheets[i]
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(sheetName);
if (sheet != null) {
callYourFunction(sheet)
}
}
Hope that helps
On Google Sheets, I want to hide rows with the value 'Del' across my whole document, meaning multiple sheets. Additionally, I would like this to only happen when I update cell E39 from Sheet 'Pricing breakdown'.
My current code is working perfectly for sheet 'Pricing breakdown', but I would like it to also affect sheets 'Project Plan' and 'Payment Plan', plus bonus points if you can help me only run this script when I change E39 from sheet 'Pricing breakdown'.
function onEdit(event){
var s = SpreadsheetApp.getActive().getSheetByName('Pricing breakdown');
s.showRows(1, s.getMaxRows());
s.getRange('A:A')
.getValues()
.forEach( function (r, i) {
if (r[0] == 'Del')
s.hideRows(i + 1);
});
}
Try this:
function onEdit(e){
var rg=e.range;
var sh=rg.getSheet();
if(sh.getName()!='Pricing breakdown'){return;}
if(e.range.getA1Notation() == 'E39') {
var shA=['Pricing breakdown','Project Plan','Payment Plan'];
for(var j=0;j<shA.length;j++) {
var s=e.source.getSheetByName(shA[j]);
s.showRows(1,s.getMaxRows());
//e.source.toast(s.getName());
s.getRange('A:A').getValues()
.forEach( function (r, i) {
if (r[0] == 'Del')
s.hideRows(i + 1);
});
}
}
}