Refreshing custom function on every change - google-apps-script

I have a custom function using DeveloperMetadata from other sheets in the workbook. The issue I have is that if I e.g. delete one of previous sheets (thus removing that metadata) the cell value doesn't change. The only trigger which recalculates this function is to make a simple whitespace change in the Script Editor and save the file.
Is there a method in Google Sheets to do this refresh automatically?
function get_prev_days(rest_key) {
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var sheets = spreadsheet.getSheets();
var sheet_index = SpreadsheetApp.getActiveSheet().getIndex();
var value = 0;
for (var i = 0; i < sheet_index; i++) {
var meta = sheets[i].getDeveloperMetadata();
for (var j = 0; j < meta.length; j++) {
if (rest_key == meta[j].getKey()) {
value += Number(meta[j].getValue());
}
}
}
return value;
}

Related

Unchecking Checkboxes in Google Sheets

For the life of me I can't figure out how set it up. I would like the function to check the entire sheet... alternatively I do know which specific ranges (B5:B26; G5:G26...AF5:AF26) on a specific sheet, if i can't set it up for the entire sheet...
function setFalse()
{
var sheet = SpreadsheetApp.getSheetByName("Test");
var dataRange = sheet.getRange('A:AN28');
var values = dataRange.getValues();
for (var i = 0; i < values.length; i++)
{
for (var j = 0; j < values[i].length; j++)
{
if (values[i][j] == true)
{
values[i][j] = false; // Modified
}
}
}
dataRange.setValues(values);
};
There's a built-in method to uncheck called uncheck(). You can effectively apply it to the sheet by using getDataRange().
function setFalse() {
SpreadsheetApp.getActive().getSheetByName("Test").getDataRange().uncheck();
};
Lastly, looking at your original code, don't forget to get a spreadsheet file before selecting the sheet.

Why does ss.deleteSheet() unhide one hidden sheet

I've written a little script that automatically deletes all Sheets my user accidentally create.
Often, when accessing the spreadsheet with their phone, they accidentally click on "+" and create tons of empty sheets, like Sheet101, Sheet102, etc.
My script is very simple: gets all sheets, and if the name starts with Sheet, just deletes it.
It works perfectly, but after deleting all the undesired sheets, the problem is that it automatically unhides the first hidden sheet (some sheets are hidden for practical use, like they contain lists).
For example, if I have the following hidden sheets:
oldform, list1, list2, ...
After executing the script, the sheet oldform will be unhidden, thus appearing to the users.
Here is two codes I've tried:
The simple one:
function DELETESHEETS2() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheets = ss.getSheets();
for (i = 0; i < sheets.length; i++) {
if ( sheets[i].getName().indexOf("Sheet") > -1 ) {
ss.deleteSheet(sheets[i]);
}
}
}
The one I tried to modify to solve my problem:
function DELETESHEETS3() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheets = ss.getSheets();
for (i = 0; i < sheets.length; i++) {
if ( sheets[i].getName().indexOf("Sheet") > -1 ) {
label = sheets[i].getName();
toto = ss.getSheetByName(label);
ss.deleteSheet(toto);
}
}
}
The reverse loop
With this one, the hidden sheet appears at the beginning and not at the end:
function DELETESHEETS2() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheets = ss.getSheets();
for (i = sheets.length -1; i > 0 ; i--) {
if ( sheets[i].getName().indexOf("Sheet") > -1 ) {
ss.deleteSheet(sheets[i]);
}
}
}
A partial solution
This one works, thanks to #OMila. But it does not explain the weird behavior of a simple loop. Maybe I missed something?
function DELETESHEETS2() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheets = ss.getSheets();
var hdnShts = [];
for (var i = 0; i < sheets.length; i++) {
if(sheets[i].isSheetHidden()) {
hdnShts.push(sheets[i].getName()); //saving all the hidden sheet names for later
}
}
for (i = 0; i < sheets.length; i++) {
if ( sheets[i].getName().indexOf("Sheet") > -1 ) {
ss.deleteSheet(sheets[i]);
}
}
for(var i = 0; i<hdnShts.length; i++) {
ss.getSheetByName(hdnShts[i]).hideSheet(); //just to make sure all the hiddens remain hidden
}
}
Thanks for your help if you have any idea!
PS: this problem is easy to reproduce, just create lots of sheets including one with a name other than Sheetxxx
Try this:
function delSheetsKeepHidden() {
var ss = SpreadsheetApp.getActive();
var shts = ss.getSheets();
var hdnShts = [];
for (var i = 0; i < shts.length; i++) {
if(shts[i].isSheetHidden()) {
hdnShts.push(shts[i].getName()); //saving all the hidden sheet names for later
}
}
for(var i = 0; i < shts.length; i++) {
if(shts[i].getName().slice(0,5).toLowerCase() == "sheet") {
ss.deleteSheet(shts[i]); //I think this is just deleting the sheet and not the array element in shts so no reason to keep track of deleted sheets like when deleting rows in a spreadsheet
}
}
for(var i = 0; i<hdnShts.length; i++) {
ss.getSheetByName(hdnShts[i]).hideSheet(); //just to make sure all the hiddens remain hidden
}
}
I've been forcing myself to get away from old school loops.
function delSheetsKeepHidden() {
var ss=SpreadsheetApp.getActive();
var shts=ss.getSheets();
var hdnShts=[];
shts.forEach(function(sht){if(sht.isSheetHidden()){hdnShts.push(sht.getName())}});
shts.forEach(function(sht){if(sht.getName().slice(0,5).toLowerCase()=="sheet"){ss.deleteSheet(sht);}});//I think this is just deleting the sheet and not the array element in shts so no reason to keep track of deleted sheets like when deleting rows in a spreadsheet
hdnShts.forEach(function(name){ss.getSheetByName(name).hideSheet();});//just to make sure all the hiddens remain hidden
}

How to delete a chart using google script?

I wrote a script to automatically generate charts from data on different tabs (after clicking a button on a new sheet).
I would now like to add a "reset" button which will automatically delete or remove the generated charts (so I can generate different charts with another button).
Can anyone help on how I can do this? I've tried the below, but to no avail....
function Finance_clear() {
function deleteAllChartsFromSheet(sheetName) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var chartsSheet = ss.getSheetByName(sheetName);
var n = chartsSheet.getCharts().length;
for (var i = n-1; i >= 0; i--) {
var chart = chartsSheet.getCharts()[i];
chartsSheet.removeChart(chart);
}
}
};
Delete All Charts from a Sheet
function delAllChartsFromSheet(name) {
var ss=SpreadsheetApp.getActive();
var sh=ss.getSheetByName(name);
var chts=sh.getCharts();
for(var i=0;i<chts.length;i++){
sh.removeChart(chts[i]);
}
}
Sheet Reference
The code worked for me, I rewrote it as:
function remove_charts_from_sheet(sheet_name) {
const tab = SpreadsheetApp.getActive().getSheetByName(sheet_name);
const charts = tab.getCharts();
for(let i = 0; i < charts.length; i++){
tab.removeChart(charts[i]);
}
}

Google spreadsheets - find all hidden rows - only include rows that are not hidden

The code provided copies data from sheet "Feuille 3" to an another sheet named "INITIALE". Some of the rows in the source sheet tab, "Feuille 3" are hidden.
I hide some rows in "Feuille 3" when col D has a checkbox with a "true" value.
I don't know how I can check the rows hidden in "Feuille 3" and remove these rows in my array "Nouvelleliste."
Here is the code :
function copiertableau() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var orig = ss.getSheetByName("Feuille 3");
var dest = ss.getSheetByName("INITIALE");
var Ancienneliste = orig.getDataRange().getValues();
var Nouvelleliste = new Array();
for (var i = 0; i < Ancienneliste.length; i++) {
var Nouvelleligne = new Array();
for (var j = 0; j < 3; j++) {
Nouvelleligne[j] = Ancienneliste[i][j];
}
Nouvelleliste[i] = Nouvelleligne;
}
ss.getSheetByName("INITIALE").getRange(22, 1, Nouvelleliste.length, 3).setValues(Nouvelleliste);
SpreadsheetApp.flush();
}
The Advanced Sheets API has a way of finding all the rows that were hidden by the user or by code. You must explicitly enable the Advanced Sheets API from the code editor. From the code editor choose, "Resources" and "Advanced Google Services." Scroll down to "Google Sheets API" Turn the button ON. Then click the link to Google API Console. Enable the Google Sheets API in your console.
First get all the rows that are hidden. In the example below, that is done in a separate function. Then compare the current row index to the values in the array. If there is a hidden row, then don't put the row's data into the array.
function copiertableau() {
var arrOfHiddenRows,Nouvelleligne,o,sourceSheetTab,ss;
ss = SpreadsheetApp.getActiveSpreadsheet();
sourceSheetTab = ss.getSheetByName("Feuille 3");
var dest = ss.getSheetByName("INITIALE");
sourceSheetTab = ss.getSheetByName("INITIALE");
var Ancienneliste = sourceSheet.getDataRange().getValues();
var Nouvelleliste = [];
o = {};//Object for arguments to pass to function to get the hidden rows
o.L = sourceSheetTab.getLastRow();
o.ssID = ss.getId();//Put the spreadsheet file ID into the object with key name ssID
o.sheetId = sourceSheetTab.getSheetId();
arrOfHiddenRows = getRowsHiddenByUsr(o);//Get a list of all hidden rows in sheet tab sourceSheet
//Logger.log('arrOfHiddenRows: ' + arrOfHiddenRows)
for (var i = 0; i < Ancienneliste.length; i++) {
if (arrOfHiddenRows.indexOf(i+1) !== -1) {//This row is hidden in the sheet sourceSheet
continue;//continue to loop without putting this rows data into the array
}
Nouvelleligne = [];
for (var j = 0; j < 3; j++) {
Nouvelleligne[j] = Ancienneliste[i][j];
}
Nouvelleliste[i] = Nouvelleligne;
}
ss.getSheetByName("INITIALE").getRange(22, 1, Nouvelleliste.length, 3).setValues(Nouvelleliste);
SpreadsheetApp.flush();
}
function getRowsHiddenByUsr(po) {
try{
var arrHiddenRows,data,fields,i,j,L,L_sh,rows,sheets,sheetId,spreadsheetId,thisSheet,thisShID;
/*
po.L - row length of the sheet tab
po.ssID - the spreadsheet file ID of the spreadsheet
po.sheetID - The ID of the sheet tab
*/
L = po.L;
spreadsheetId = po.ssID;
sheetId = po.sheetID;
//Logger.log(L)
//Logger.log('sheetId: ' + sheetId)
arrHiddenRows = [];
fields = "sheets(data(rowMetadata(hiddenByUser)),properties/sheetId)";//Get only metadata of hidden rows by user
sheets = Sheets.Spreadsheets.get(spreadsheetId, {fields: fields}).sheets;
L_sh = sheets.length;
//Sheets.Spreadsheets.get(spreadsheetId)
//Logger.log('sheets.length: ' + sheets.length)
for (i = 0; i < L_sh; i++) {
thisSheet = sheets[i];
//Logger.log('thisSheet === undefined: ' + thisSheet === undefined)
if (thisSheet === undefined) {
continue;
}
thisShID = thisSheet.properties.sheetId;
//Logger.log('thisShID: ' + thisShID)
if (thisShID === sheetId) {
//Logger.log('they are equal')
data = thisSheet.data;
rows = data[0].rowMetadata;
//Logger.log('thisShID: ' + thisShID)
//Logger.log('rows.length: ' + rows.length)
for (j = 0; j < L; j++) {
//Logger.log(rows[j].hiddenByUser)
if (rows[j].hiddenByUser) arrHiddenRows.push(j+1);
}
}
}
return arrHiddenRows;
}catch(e) {
console.log(e.message);
console.log(e.stack);//log the stack
}
}
thanks #SandyGood It seems it's missing something.
Firstly, i changed
var Ancienneliste = sourceSheet.getDataRange().getValues();
to put "sourceSheetTab" variable; and i delete a line because you put twice "sourceSheetTab" with 2 contents so i keep the first one.
Secondly, your function continue to copy hidden rows in the sourcesheet table.
maybe i can put my other code i did to hide rows in "Feuille 3" to help?
//global variables
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("Feuille 3");
function cacherRow() {
for (var i=1; i < 300; i ++){
var nb = sheet.getRange('D'+i).getValue();
if (nb == true){
sheet.hideRows(i);
}
}
}
i enable googlesheets API in the console.
Google Apps Script now offers a new method - isRowHiddenByUser - to determine if a particular row is hidden or not.
for (var i = 0; i < Ancienneliste.length; i++) {
if (sheet.isRowHiddenByUser(i+1)) {
// do something
}
}
Note that you need to specify the row position in the method and it begins from 1, not 0.

Delete Row from all google sheets based on value of a cell

I am trying to achieve a script that will delete a row based upon a string in a cell within that row,
I can achieve my mission if I want to name each individual sheet, however my sheets are constantly changing names, so this would mean updating the the script daily.
I have tried to create a loop, but that does not seem to work either, any suggestions?
The script I am working on is below.
function RemoveInvoiced() {
var ss = SpreadsheetApp.openById("1pxWr3jOYZbcwlFR7igpSWa9BKCa2tTeliE8FwFCRTcQ").getSheets();
for (var k=0; k<ss.length; k++)
var values = ss[k].getDataRange().getValues();
var rows_deleted = 0;
for (var i = 0; i < values.length; i++) {
for (var j = 0; j < values[i].length; j++) {
var value = values[i][j];
var row = i + 1 - rows_deleted;
if (typeof value === 'string') {
var result = value.search("INVOICED");
if (result !== -1) {
ss.deleteRow(row);
rows_deleted++;
}
}
}
}
};
Instead of using the sheet name to access them, you could access them by Id.
The id will never change as long as you don't delete the sheet, even if you change the name.
You'd first have to get the sheets Id, a simple function could do that for you
function getId(sheet){
Logger.log(sheet.getSheetId());
}
Once you have gotten your sheet ids, you can make a function that will open the right sheet by the ID
function openSheetById(doc, id){
var sheets = doc.getSheets();
for(var i = 0; i< sheets.length ; i++){
if( sheets[i].getSheetId() == id)
return sheets[i];
}
return false;
}
And then you can access the right sheet.
function myFunc(){
var doc = SpreadsheetApp.openById("1pxWr3jOYZbcwlFR7igpSWa9BKCa2tTeliE8FwFCRTcQ");
var id = // your id here
var sheet = openSheetById(doc, id);
if(sheet){
//do other stuffs
}
}