How to affect multiple sheets from a script - google-apps-script

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);
});
}
}
}

Related

Google spreadsheet script remove protection in certain cells

Let's say certain cells will be protected if it matches certain condition
function A() {
var sh = SpreadsheetApp.getActiveSpreadsheet();
var ss = sh.getActiveSheet();
var protection;
for(var i = 3;i<ss.getLastRow();i=i+1){
if(ss.getRange(i,4).getValue()==7){ // check if the condition is matched
for(var j=5; j<=20; j=j+2){
if(ss.getRange(i,j).isBlank()){
protection = ss.getRange(i,j).protect(); // protect that certain cell
protection.removeEditors(protection.getEditors());
if (protection.canDomainEdit())
protection.setDomainEdit(false);
}
}
}
}
Now I want to delete the protection I made on that cell if the condition changed, let's say the upcoming code will be:
if(ss.getRange(i,4).getValue()<=6){
...
How can I only delete the protection applied to that cell instead of all the protection in the entire sheet?
Thank you
I tried
getProtections().remove()
but it does not target certain cells right?
I believe your goal is as follows.
You want to unprotect the cells when the value of column "D" is less than 6.
About Now I want to delete the protection I made on that cell if the condition changed, when I saw your showing script for protecting the cells, it seems that when the if-statement of if(ss.getRange(i,4).getValue()==7){} is true, the empty cells of the row are protected. From this situation, in your goal, when the if statement of if(ss.getRange(i,4).getValue()<=6){} is true, you want to remove all protected cells of the row.
If my understanding is correct, how about the following sample script? In this case, the protected ranges can be retrieved with getProtections(SpreadsheetApp.ProtectionType.RANGE).
Sample script 1:
In this sample, when this script is run, the protected cells are unprotected by searching the values of column "D".
function myFunction() {
var sh = SpreadsheetApp.getActiveSpreadsheet();
var ss = sh.getActiveSheet();
var protects = ss.getProtections(SpreadsheetApp.ProtectionType.RANGE).reduce((o, p) => {
var row = p.getRange().getRow();
o[row] = o[row] ? [...o[row], p] : [p];
return o;
}, {});
var values = ss.getRange(3, 4, ss.getLastRow() - 2, 1).getValues();
values.forEach(([d], i) => {
if (d <= 6) {
var obj = protects[i + 3];
if (obj) {
obj.forEach(p => p.remove());
}
}
});
}
Sample script 2:
In this sample, when the cells of column "D" is edited, the script is run by the installable OnEdit trigger. So, please install the OnEdit trigger to the function installedOnEdit. When you use this script, please edit the cells of column "D". By this, the script is run. When this script is directly run by the script editor, an error occurs. Please be careful about this.
function installedOnEdit(e) {
var sheetName = "Sheet1"; // Please set your sheet name.
var { range } = e;
var sheet = range.getSheet();
if (sheet.getSheetName() != sheetName || range.columnStart != 4 || range.rowStart < 3 || range.getValue() > 6) return;
var protects = sheet.getProtections(SpreadsheetApp.ProtectionType.RANGE).reduce((o, p) => {
var row = p.getRange().getRow();
o[row] = o[row] ? [...o[row], p] : [p];
return o;
}, {});
var obj = protects[range.rowStart];
if (obj) {
obj.forEach(p => p.remove());
}
}
Note:
When I saw your script A(), I thought that when the script is modified, the process cost might be able to be reduced a little. So, how about the following modification?
function A() {
var sh = SpreadsheetApp.getActiveSpreadsheet();
var ss = sh.getActiveSheet();
var values = ss.getRange(3, 1, ss.getLastRow() - 2, 20).getValues();
values.forEach((r, i) => {
if (r[3] == 7) {
for (var j = 4; j < 20; j = j + 2) {
if (r[j].toString() == "") {
protection = ss.getRange(i + 3, j + 1).protect();
protection.removeEditors(protection.getEditors());
if (protection.canDomainEdit())
protection.setDomainEdit(false);
}
}
}
});
}
References:
getProtections(type)
reduce()
forEach()
Installable Triggers

Using Checkbox to Transpose Paste onto Target Range using Google Sheets

The script inserted copies the values in range Sheet1!A5:F5 then transpose pastes into the first row and second column of Sheet2.
The script is able to copy and transpose paste under the last row of the second column on continuous use until it is reset.
.
Current Result Before Paste - Copies Range Sheet1 A5:F5
Current Result After Paste - Pastes to Range 2nd Column in Sheet2
.
.
.
My aim would be:
for the script to work with the checkbox in cell B2.
to create a target range for the values to be pasted to:
Sheet2! B7:B - hopefully still allowing the transpose paste to function through getLastRow()+1.
Expected Result Before Paste - Checkbox = True, Copying Range Sheet1 A5:F5
Expected Result After Paste - Paste to Range Sheet2 B7:B
Have tried inserting:
if (sheet.getSheetName() != "*DuplioMltSlds" || range.getA1Notation() != "D13" || !range.isChecked()) return;
as well as changing the function to onEdit.
Have also tried various var row = ss.getRange("Sheet2!B7:B") options, however, have not been able find a suitable result.
Sample Sheet
Script source and script:
function copyTransposeAndDelete () {
var ss = SpreadsheetApp.getActiveSpreadsheet ();
var source = ss.getRange("Sheet1!A5:F5");
var destSheet = ss.getSheetByName("Sheet2");
var destRange = destSheet.getRange(destSheet.getLastRow()+1, 2, source.getWidth(), source.getHeight());
destRange.setValues(transpose(source.getValues()));
}
function transpose(array) {
return array[0].map(function(row, i) {
return array.map(function(col) {
return col[i];
});
});
}
I believe your goal is as follows.
You want to copy and paste the cells "A5:F5" of "Sheet1" to the cell "B2" of "Sheet2". In this case, you want to copy the values by transposing.
When the values are copied again, you want to paste the transposed values to (the last row + 1) of column "B" of "Sheet2".
You want to run the script when the checkbox of cell "B2" of "Sheet1" is checked.
In this case, how about the following modification?
Modified script:
function onEdit(e) {
// Ref: https://stackoverflow.com/a/44563639
Object.prototype.get1stNonEmptyRowFromBottom = function (columnNumber, offsetRow = 1) {
const search = this.getRange(offsetRow, columnNumber, this.getMaxRows()).createTextFinder(".").useRegularExpression(true).findPrevious();
return search ? search.getRow() : offsetRow;
};
var { source, range } = e;
var sheet = range.getSheet();
if (sheet.getSheetName() != "Sheet1" || range.getA1Notation() != "B2" || !range.isChecked()) return;
var srcRange = sheet.getRange("A5:F5");
var dstSheet = source.getSheetByName("Sheet2")
var row = dstSheet.get1stNonEmptyRowFromBottom(2) + 1;
var dstRange = dstSheet.getRange("B" + (row < 7 ? 7 : row));
srcRange.copyTo(dstRange, SpreadsheetApp.CopyPasteType.PASTE_VALUES, true);
}
In your situation, I thought that copyTo(destination, copyPasteType, transposed) might be suitable. Because, in this method, the values can be copied by transposing.
When the OnEdit trigger is used, you can use the event object. By this, the process cost will become low a little. Ref
Note:
When you use this script, please check the checkbox of "B2" of "Sheet1". By this, the script is run. When you directly run the script with the script editor, an error like TypeError: Cannot destructure property 'source' of 'e' as it is undefined. occurs. Please be careful about this.
References:
copyTo(destination, copyPasteType, transposed)
Event Objects
Added:
If you want to put the values to the column "B" of "Sheet2" even when the column "B" of "Sheet2" has already had the values after row 7, how about the following sample script?
Sample script:
function onEdit(e) {
// Ref: https://stackoverflow.com/a/44563639/7108653
Object.prototype.get1stEmptyRowFromTop = function (columnNumber, offsetRow = 7) {
const range = this.getRange(offsetRow, columnNumber, 2);
const values = range.getDisplayValues();
if (values[0][0] && values[1][0]) {
return range.getNextDataCell(SpreadsheetApp.Direction.DOWN).getRow() + 1;
} else if (values[0][0] && !values[1][0]) {
return offsetRow + 1;
}
return offsetRow;
};
var { source, range } = e;
var sheet = range.getSheet();
if (sheet.getSheetName() != "Sheet1" || range.getA1Notation() != "B2" || !range.isChecked()) return;
var srcRange = sheet.getRange("A5:F5");
var dstSheet = source.getSheetByName("Sheet2")
var row = dstSheet.get1stEmptyRowFromTop(2);
var dstRange = dstSheet.getRange("B" + row);
srcRange.copyTo(dstRange, SpreadsheetApp.CopyPasteType.PASTE_VALUES, true);
}
function myfunk() {
const ss = SpreadsheetApp.getActive();
const sh = ss.getSheetByName("Sheet1")
const vs = sh.getRange("A4:F4").getValues().flat().map(e => [e]);
const dsh = ss.getSheetByName("Sheet2");
dsh.getRange(7,2,vs.length,vs[0].length).setValues(vs)
}

Running a script by checking a checkbox on two different sheets

I'm having odd behavior on my code.
I need the script to trigger when I'm using an Ipad or a mobile phone.
I was able to have it work on one sheet "Existing client". Now I'm trying to have a second sheet "new client" that also has the same behavior but triggers others functions.
The thing is it looks like when I switch between sheets, the checkbox needs to be checked multiple times and does not trigger all the time. My solution would be to simply make another spreadsheet for the second sheet "new client" but I would still like to understand what is wrong with what I did?
Here is the code:
So I reduce the code to a minimum, meaning if i check the box, I have the time stamp in a cell. If I have two sheets, it is not working.
Thanks for your help,
j.
function onEditTriggerClientExistant() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Client existant");
var range = sheet.getActiveRange()
if (range.isChecked()) {
if (range.getA1Notation() == "B4") {
sheet.getRange('C4').setValue(new Date());
range.uncheck();
} else {
range.uncheck()
}
}
}
function onEditTriggerNouveauClient() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Nouveau Client");
var range = sheet.getActiveRange()
if (range.isChecked()) {
if (range.getA1Notation() == "A4") {
sheet.getRange('B4').setValue(new Date());
range.uncheck();
} else {
range.uncheck()
}
}
}
var ss = SpreadsheetApp.getActiveSpreadsheet()
function onEdit(e) {
let oldValue = e.oldValue;
let newValue = e.value;
var r = e.range.getRow()
var c = e.range.getColumn()
var active_sheet = ss.getActiveSheet()
var sheetName = active_sheet.getName();
if(sheetName=="sheet1" && r==4 && c==2){
}
else if(sheetName=="sheet2" && r==4 && c==1){
}
}

Google Script for Conditional Formatting w/ multiple ranges and more than one condition

Sample of the page I am usingI use a conditional format across my sheets but want to change it to Google Script instead.
For every check out date that does not have a Y, highlight yellow.
I managed to modify a script to highlight a cell based on two conditions for A:D but cannot figure out how to extend it to the other ranges in my sheet.
Here is what I have so far that I found :
function onEdit(e) {
var sheet = SpreadsheetApp.getActiveSheet();
var range = sheet.getRange("A:D");
var values = range.getValues();
//for each row that data is present
for(var i = 0; i < values.length; i++) {
var cell = sheet.getRange(i + 1, 4);
if(values[i][1] !== "") {
if(values[i][3] === "") {
cell.setBackground('yellow');
} else {
cell.setBackground(null);
}
} else {
cell.setBackground(null);
}
}
}
Links: Google App Script - Conditional Formatting Based on Another Cell
Try this:
function onEdit(e) {
//e.source.toast('Entry');
var sh=e.range.getSheet();
if(sh.getName()=="Sheet2") {
//e.source.toast('Conditional');
var vs=sh.getRange(1,1,sh.getLastRow(),4).getValues();
vs.forEach(function(r,i){
let rg=sh.getRange(i+1,4);
if(r[1]=="") {
if(r[3]=="") {
//e.source.toast('yellow');
rg.setBackground('#ffff00');
}else{
//e.source.toast('white');
rg.setBackground('#ffffff');
}
}else{
//e.source.toast('r[1] not null');
rg.setBackground('#ffffff');
}
});
}
}
I tested this a little and it seems to work
function onEdit(e) {
//e.source.toast('Entry');
var sh=e.range.getSheet();
if(sh.getName()=="Sheet2") {
//e.source.toast('Conditional');
var v=sh.getRange(6,1,sh.getLastRow()-5,sh.getLastColumn()).getValues();
var r0=sh.getRange(6,4,sh.getLastRow()-5,1);
var c0=r0.getBackgrounds();
var r1=sh.getRange(6,9,sh.getLastRow()-5,1);
var c1=r1.getBackgrounds();
var r2=sh.getRange(6,14,sh.getLastRow()-5,1);
var c2=r2.getBackgrounds();
var r3=sh.getRange(6,19,sh.getLastRow()-5,1);
var c3=r3.getBackgrounds();
var r4=sh.getRange(6,24,sh.getLastRow()-5,1);
var c4=r4.getBackgrounds();
v.forEach(function(r,i){
c0[i][0]=(r[1]=="" && r[3]=="")?'#ffff00':'#ffffff';
c1[i][0]=(r[6]=="" && r[8]=="")?'#ffff00':'#ffffff';
c2[i][0]=(r[11]=="" && r[13]=="")?'#ffff00':'#ffffff';
c3[i][0]=(r[16]=="" && r[18]=="")?'#ffff00':'#ffffff';
c4[i][0]=(r[21]=="" && r[23]=="")?'#ffff00':'#ffffff';
});
r0.setBackgrounds(c0);
r1.setBackgrounds(c1);
r2.setBackgrounds(c2);
r3.setBackgrounds(c3);
r4.setBackgrounds(c4);
}
}
Try this for the entire spreadsheet:
function onEdit(e) {
//e.source.toast('Entry');
var sh=e.range.getSheet();
//e.source.toast('Conditional');
var v=sh.getRange(6,1,sh.getLastRow()-5,sh.getLastColumn()).getValues();
var r0=sh.getRange(6,4,sh.getLastRow()-5,1);
var c0=r0.getBackgrounds();
var r1=sh.getRange(6,9,sh.getLastRow()-5,1);
var c1=r1.getBackgrounds();
var r2=sh.getRange(6,14,sh.getLastRow()-5,1);
var c2=r2.getBackgrounds();
var r3=sh.getRange(6,19,sh.getLastRow()-5,1);
var c3=r3.getBackgrounds();
var r4=sh.getRange(6,24,sh.getLastRow()-5,1);
var c4=r4.getBackgrounds();
v.forEach(function(r,i){
c0[i][0]=(r[1]=="" && r[3]=="")?'#ffff00':'#ffffff';
c1[i][0]=(r[6]=="" && r[8]=="")?'#ffff00':'#ffffff';
c2[i][0]=(r[11]=="" && r[13]=="")?'#ffff00':'#ffffff';
c3[i][0]=(r[16]=="" && r[18]=="")?'#ffff00':'#ffffff';
c4[i][0]=(r[21]=="" && r[23]=="")?'#ffff00':'#ffffff';
});
r0.setBackgrounds(c0);
r1.setBackgrounds(c1);
r2.setBackgrounds(c2);
r3.setBackgrounds(c3);
r4.setBackgrounds(c4);
}
Keep in mind if you have multiple people editing the sheet then you are going to be missing some edits because the script will not be able to keep up.

How to merge scripts so one stops undoing the other

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: