how do you apply code to all tabs on a google sheet - google-apps-script

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

Related

Delete and add columns in google sheet

I am using the below code to create the validations in google sheet (contributed by Cooper), what this script does is it automatically check the applicable headers and create the dropdown with values and hide the columns which are not applicable.
I am trying to solve here is:
The script checks the applicable headers related to the Product Selection
It creates the dropdown with validation values
Instead of hiding the not applicable columns, It removes them from the sheet
I am a beginner to google script and tried using the deletecolumn function but unable to get it work.
Please help me out here.
function loadObjectsAndCreateProductDropDown() {
const ss = SpreadsheetApp.getActive();
const sh = ss.getSheetByName('Sheet0');
const psh = ss.getSheetByName('Sheet1');
const [ph, ...prds] = sh.getRange(1, 1, 10, 6).getValues().filter(r => r[0]);
const [ch, ...chcs] = sh.getRange(11, 1, 10, 10).getValues().filter(r => r.join());
let pidx = {};
ph.forEach((h, i) => { pidx[h] = i });
let prd = { pA: [] };
prds.forEach(r => {
if (!prd.hasOwnProperty(r[0])) {
prd[r[0]] = { type: r[pidx['Type']], size: r[pidx['Size']], color: r[pidx['Color']], material: r[pidx['Material']], length: r[pidx['Length']] };
prd.pA.push(r[0]);
}
});
let cidx = {};
let chc = {};
ch.forEach((h, i) => { cidx[h] = i; chc[h] = [] });
chcs.forEach(r => {
r.forEach((c, i) => {
if (c && c.length > 0) chc[ch[i]].push(c)
})
})
const ps = PropertiesService.getScriptProperties();
ps.setProperty('product_matrix', JSON.stringify(prd));
ps.setProperty('product_choices', JSON.stringify(chc));
Logger.log(ps.getProperty('product_matrix'));
Logger.log(ps.getProperty('product_choices'));
psh.getRange('A2').setDataValidation(SpreadsheetApp.newDataValidation().requireValueInList(prd.pA).build());
}
//I chose to use an installable dropdown. I'm not sure if it's needed. Its up to you.
function onMyEdit(e) {
//e.source.toast('entry')
const sh = e.range.getSheet();
if (sh.getName() == 'Sheet1' && e.range.columnStart == 1 && e.range.rowStart == 2 && e.value) {
//e.source.toast('flag1');
sh.getRange('C2:G2').clearDataValidations();
let ps = PropertiesService.getScriptProperties();
let prodObj = JSON.parse(ps.getProperty('product_matrix'));//recovering objects from PropertiesService
let choiObj = JSON.parse(ps.getProperty('product_choices'));
let hA = sh.getRange(1, 1, 1, sh.getLastColumn()).getDisplayValues().flat();
let col = {};
hA.forEach((h, i) => { col[h.toLowerCase()] = i + 1 });
["type", "size", "color", "material", "length"].forEach(c => {
if (choiObj[prodObj[e.value][c]]) {
sh.getRange(e.range.rowStart, col[c]).setDataValidation(SpreadsheetApp.newDataValidation().requireValueInList(choiObj[prodObj[e.value][c]]).build()).offset(-1,0).setFontColor('#000000');
sh.showColumns(col[c])
} else {
sh.getRange(e.range.rowStart, col[c]).offset(-1,0).setFontColor('#ffffff');
sh.hideColumns(col[c]);
}
})
}
}
demo sheet
https://docs.google.com/spreadsheets/d/18guAXXjWIMDQilX8Z0Y4_Avogjs2ESMbrZY7Sb9TaxE/edit#gid=0
Sample Data
P1
Type
Size
Color
Material
Length
Kurta Pyjamas
No
Sizeethnic_1
Colorethnic_1
Materialethnic_3
Lengthethnic_1
Dhotis
Typethnic_1
No
Colorethnic_2
Materialethnic_2
No
Sherwani
No
No
Colorethnic_2
No
Lengthethnic_2
Men Pyjamas
Typeethnic_2
No
Colorethnic_2
No
No
Kurtas
No
Sizeethnic_2
Colorethnic_1
No
Lengthethnic_1
Ethnic Jackets
No
No
Colorethnic_1
No
No
Typethnic_1
Typeethnic_2
Sizeethnic_1
Sizeethnic_2
Colorethnic_1
Colorethnic_2
Materialethnic_3
Materialethnic_2
Lengthethnic_1
Lengthethnic_2
Mundu
Churidar
XS
XS
Beige
Green
Blended
Silk Blend
Above Knee
Short
Regular Dhoti
Regular Pyjama
S
S
Black
Grey
Cotton
Velevt
Ankle Length
Medium
Patiala
M
M
Blue
Maroon
Dupion
Viscose Rayon
Jodhpuri
L
L
Brown
Multi
Wool
Harem
XL
XL
Copper
Mustard
XXL
XXL
Cream
3XL
3XL
Gold
Suggestion
This may not be the cleanest code but you may try this implementation below. Instead of removing columns, it will clear the Sheet1 headers and their corresponding drop-downs on every new selection on the A2 drop-down.
NOTE: Since your sample data will increase in size overtime, this setup will need you to put the data into a separate sheet tab for a cleaner setup, such as this sample below:
Data1 sheet tab:
Data2 sheet tab:
UPDATED
Sample Script
var sh = SpreadsheetApp.getActiveSpreadsheet();
var selection = sh.getSheetByName("Sheet1").getRange("A2").getValue(); //Get the selection on the dropdowm on cell A2
var data1 = sh.getSheetByName("Data1").getDataRange().getDisplayValues();
var data2 = sh.getSheetByName("Data2").getDataRange().getDisplayValues();
function addHeaders(sheet, values) { //Adds the headers from Column C and beyond
var startCol = 3; //Column C
var endCol = startCol + values.length;
values.forEach(x => {
if(startCol <= endCol){
if(checkHeaderIfYesOrNo(x) == true)return;
sheet.getRange(1,startCol).setValue(x);
startCol += 1;
}
});
}
function onEdit(e) {
if(e.range.getA1Notation() != "A2")return;//Make sure to run onEdit function ONLY when cell A2 is edited/selected
var headers = [];
var headerValues = [];
var temp = [];
var counter = 0;
clean();
data1 = fixDuplicates();
//find selection name on data1
for(var x = 0; x< data1.length; x++){
var name = data1[x][0];
if(name == selection){
///get the headers & their values
data1[x].forEach(res => {
if(res != "" & res != selection){
var index1 = data1[x].indexOf(res);
var index2 = data2[0].indexOf(res);
headers.push([data1[0][index1]]);
for(var y=0; y< data2.length; y++){
if(data2[y][index2] != "" && data2[y][index2] != res){
temp.push("**"+res+"**"+data2[y][index2]); //place raw header data to a temporary variable
}
}
//Set the drop-down data of each headers
temp.forEach(raw => { //clean the temp array
if(raw.includes(res)){
var regex = /\*\*([^*]*(?:\*(?!\*)[^*]*)*)\*\*/g;
headerValues.push(raw.replace(regex, ""))
}
});
//Logger.log("Data of the \""+res+"\" header:\n"+headerValues);
//set data validation per header
counter += 1;
if(res.toLowerCase().includes("no"))return; //skip creating data validation for "No" header
if(res.toLowerCase().includes("yes")) return; //skip creating data validation for "Yes" header
sh.getSheetByName("Sheet1").getRange(2,2+counter).setDataValidation(SpreadsheetApp.newDataValidation().requireValueInList(headerValues).build());
headerValues = [];
}
});
}
}
addHeaders(sh.getSheetByName("Sheet1"), headers);
}
function clean(){ //Clean Sheet 1 on every edit
sh.getSheetByName("Sheet1").getRange('C2:Z').clearDataValidations();
sh.getSheetByName("Sheet1").getRange('C1:Z').clearContent();
}
function fixDuplicates(){
var temp = [];
var data1New = [];
var count = 1;
for(var x=0; x<data1.length; x++){
data1[x].forEach(findIt => {
if(findIt.toLowerCase().includes("yes") || findIt.toLowerCase().includes("no")){
temp.push(findIt+count);
count += 1;
}else{
temp.push(findIt);
}
})
data1New.push(temp);
temp=[];
}
return data1New;
}
function checkHeaderIfYesOrNo(h){
for(var x=0; x<data1.length; x++){
if(data1[x][0] == selection){
if(data1[x][data1[0].indexOf(h.toString())].toLowerCase().includes("yes")){
Logger.log(h +" contains Yes");
return null;
}else if(data1[x][data1[0].indexOf(h.toString())].toLowerCase().includes("no")){
Logger.log(h+" header will not be added as it has \"No\" value");
return true;
}else{
Logger.log("Skip the "+h +" header");
return null;
}
}
}
}
Sample Demonstration:
Sample Execution Log result for review:

apps script : how can I simplify to avoid multiple calls to server?

I cannot find the way to properly simplify the nested loops to build an array of values and data validations and then set them all in the sheet in a single call to the server.
Is it even possible ??
function onEdit(){
testValidation()
}
function testValidation() {
var sheet = SpreadsheetApp.getActiveSheet();
var source = SpreadsheetApp.getActive().getRange('A3:J4').getValues()
var destination = SpreadsheetApp.getActive().getRange('M3:V4');
destination.clearDataValidations();
var validationRule = SpreadsheetApp.newDataValidation().requireCheckbox().build(); // checkbox
for(var r = 0; r <= source.length - 1; r++) {
for(var c = 0; c <= source[0].length - 1; c++) {
if(source[r][c] ==="" ){
sheet.getRange(r + 3,c + 14).clearDataValidations().setValue(null)
}else{
sheet.getRange(r + 3,c + 14).setDataValidation(validationRule).setValue("true")
}
}
}
}
Link to shared spreadsheet :
https://docs.google.com/spreadsheets/d/1fyFPIssp3zUjRmWxU9LqHvpowH8SHdMQYizNOZ3xKsA/edit?usp=sharing
In your situation, how about the following modified script?
Modified script 1:
function testValidation() {
var check = SpreadsheetApp.newDataValidation().requireCheckbox().build();
var sheet = SpreadsheetApp.getActiveSheet();
var source = sheet.getRange('A3:J4').getValues();
var values = source.map(r => r.map(c => c != "" ? check : null));
sheet.getRange('M3:V4').clearContent().setDataValidations(values).check();
}
In this modification, the checkboxes and the clear are set by setDataValidations.
I thought that this method might be low process cost.
Modified script 2:
function testValidation() {
// Ref: https://stackoverflow.com/a/21231012/7108653
const columnToLetter = column => {
let temp,
letter = "";
while (column > 0) {
temp = (column - 1) % 26;
letter = String.fromCharCode(temp + 65) + letter;
column = (column - temp - 1) / 26;
}
return letter;
};
var sheet = SpreadsheetApp.getActiveSheet();
var source = sheet.getRange('A3:J4').getValues();
var rangeList = source.flatMap((r, i) => r.flatMap((c, j) => c != "" ? `${columnToLetter(j + 14)}${i + 3}` : []));
sheet.getRange('M3:V4').clearDataValidations().setValue(null);
if (rangeList.length == 0) return;
sheet.getRangeList(rangeList).insertCheckboxes().check();
}
I thought that the cells of M3:V4 can be cleared using sheet.getRange('M3:V4').clearDataValidations().setValue(null).
In this modification, the checkboxes are put using insertCheckboxes() method of Class RangeList.
References:
insertCheckboxes()
setDataValidations()

Google script - optimization (populate multiple columns if cell value)

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

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:

Check duplicates between all sheets in google sheets

What I want to do is check for duplicates between all google sheets and if there are any - mark them in a color. For now, I left the headers to my columns just to see if it marks them red. I found one script that everyone said that worked - however, I got really random results with it. Could you possibly help with it?
The script:
Array.prototype.countItem = function (item) {
var counts = {};
for (var i = 0; i < this.length; i++) {
var num = this[i];
counts[num] = counts[num] ? counts[num] + 1 : 1;
}
return counts[item] || 0;
}
function findDuplicatesOnAllSheets() {
var nondupes = [];
SpreadsheetApp.getActive()
.getSheets()
.forEach(function (s) {
s.getRange('A1:E500')
.getValues()
.reduce(function (a, b) {
return a.concat(b);
})
.forEach(function (x, i, v) {
if (x && nondupes.countItem(x) == 0) {
nondupes.push(x)
} else if (x && nondupes.countItem(x) >= 1) {
s.getRange(i + 1, 3)
.setBackground('red');
}
});
});
}
This is the sheet I tested it on and you can see starting from sheet 3 that the results were random. The file should only have the headers as duplicates.
https://docs.google.com/spreadsheets/d/1naYaxR0f_wGsRP4-nBRZfAJ9ZqxpyA1uqQ9Uz5xvwNs/edit#gid=1044860260