Google Sheets: noticing and acting upon new table added and table renamed - google-apps-script

The title is the question.
I'm working on a spreadsheet in Google Drive where we got a central sheet named Name registry with names of the people who ever visited some place where notebooks are left at for them to write their name down on. The sheet calculates various stats, too.
The sheets for different places will be named as whatever the specific notebooks were named. The name registry saves us writing and fixing by referencing the cell of a particular name.
I'm sure there won't be anywhere like 255 sheets (which should be a lifted limit in the new Sheets), but I came across an idea how to have it automated:
1) Optionally a new sheet is added and the spreadsheet notices it.
2) Once either
2.1) the new sheet is renamed, the `Name registry` will auto-name one of the free columns as the renamed sheet; or
2.2) an old sheet is renamed, the sheet's old column is renamed in the `Name registry`
I did check on things, but the documentation is, frankly, brainlessly organized, so the only option is to use Google on its own resources.
For example, https://developers.google.com/apps-script/understanding_events implies it can't be done anyhow.

You should know that there is, indeed, a trigger that fires when inserting one sheet (I'm guessing that's what you call "table" in your question).
Re-check the docs for the spread sheet Change event, this event only fires if you use an installed trigger. To use it (copied from the docs)
Open or a create a new spreadsheet.
Click the Unsaved spreadsheet dialog box and change the name.
Choose Tools > Script Editor and write the function you want to run.
Choose Resources > Current project's triggers. You see a panel with the message No triggers set up. Click here to add one now.
Click the link.
Under Run, select the function you want executed by the trigger (for example myFunction).
Under Events, select From Spreadsheet
From the next drop-down list, select On Change.
Click Save.
Please notice that this trigger is not the same as the simple onEdit one.
Also, bear in mind that your callback function must receive the event parameter:
function myFunction(e) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
ss.getSheetByName("Sheet1").getRange("A1").setValue(e.changeType);
}
You'll see that there are different types of changes: EDIT, INSERT_ROW, INSERT_COLUMN, REMOVE_ROW, REMOVE_COLUMN, INSERT_GRID, REMOVE_GRID, OTHER. You're looking for INSERT_GRID for inserting new sheet and OTHER for renaming it.
Having said that, the problem here will be detecting the rename of one sheet as its an OTHER type, so you may easily trigger the function when you don't really want to do it (for example changing background color, also fires the onChange event with OTHER edit type). My advice is that you don't try to detect the createion/change but create a custom menu entries for handling the creation and renaming of sheets:
function onOpen()
{
var ss = SpreadsheetApp.getActiveSpreadsheet();
var menuEntries = [ {name: "Create sheet", functionName: "createNewSheet"},
{name: "Rename current Sheet", functionName: "renameCurrentSheet"}
];
ss.addMenu("Custom operations", menuEntries);
}
function createNewSheet()
{
// create the new sheet
// also execute the logic you want when new sheet is created
}
function renameCurrentSheet()
{
// rename current sheet
// also execute the logic you want when sheet is renamed
}
Hope I put some light to your problem.

Related

Apps Script - How could this code be streamlined?

I've recently started working with Apps Script to improve the scope of what my google sheets can do, and I wanted to ask more experienced people how I might make my script more efficient. I used a mixture of tutorials, documentation, and trial & error to make it. I find that although it usually completes the task it's meant for, sometimes it takes an unreasonably long time or exceeds its runtime and simply stops.
I would like to know which best practices I could implement to make it run more quickly overall, and which things I might be able to include in future scripts to avoid any pitfalls I'd landed in here.
Scope:
The script is meant to take each day's new data and apply it to a new sheet called 'TODAY.' It works as follows.
Rename the tab labeled 'TODAY' to the previous workday's date (if today is 2.3, it renames the sheet to 2.2.)
Hide this renamed tab.
Duplicate the 'TEMPLATE' tab, and rename it to 'TODAY.'
Pull data from the 'RAW DATA' tab, and paste it into the new 'TODAY' tab.
Paste a formula into the new 'TODAY' tab and drag it down to the bottom of the table so that the correct values populate and the conditional formatting occurs.
Any help would be greatly appreciated, I really just need some direction for how to improve my work.
Here is a link to an example sheet with editing permissions enabled: https://docs.google.com/spreadsheets/d/1F7bAd2DjKgk53e-haPgjWfFphMfu5YBn8iRQ3qwC3n0/edit?usp=sharing
In my humble opinion, a good Google Sheet App Script doesn't need to use activate to control the source or destination of data. The sheet and script developer should know what and where they want the data to come from and go. Activate is like using the mouse to click on something.
I've taken your script and rewritten to minimize the use of variables. I have only one sheet variable and reuse it throughout. In fact for the majority of the time it is the copy of the TEMPLATE called TODAY.
Also unless I have to use a sheet last row many times, I avoid using a variable and instead just use sheet.getLastRow(). Same for columns.
I always wrap my code in a try catch block as a matter of habit.
As a last note, unless you change the notation in column C and N you could have used your script to fill in column B.
function myDailyUpdate() {
try {
let spread = SpreadsheetApp.getActiveSpreadsheet();
// Step 1
let sheet = spread.getSheetByName("TODAY");
let oldDate = sheet.getRange("Q4").getValue();
let prevDate = Utilities.formatDate(oldDate,"GMT-5","M.d");
// Renames old 'TODAY' sheet to previous workday's date.
sheet.setName(prevDate);
// Sets the color to red.
sheet.setTabColor("990000");
// Hides the old 'TODAY' sheet
sheet.hideSheet();
// Step 2
sheet = spread.getSheetByName("TEMPLATE");
// Copies the contents of the 'TEMPLATE' sheet to a new sheet called 'TODAY.'
sheet = sheet.copyTo(spread);
sheet.setName("TODAY");
sheet.activate(); // required to move to 1st position
// Move TEMPLATE to first position
spread.moveActiveSheet(1);
// Step 3
// Colors the 'TODAY' tab green to signify it being active.
sheet.setTabColor("6aa85f")
// Identifies the 'RAWDATA' sheet for later use.
let source = spread.getSheetByName("RAWDATA");
// Identifies ranges and values for later use.
let values = source.getDataRange().getValues();
// sheet is still the "TODAY" sheet
// Identifies 'TODAY' sheet as the recipient of 'RAWDATA' data, and identifies the range.
// Sets the values from 'RAWDATA' into 'TODAY.'
sheet.getRange(12,2,values.length,values[0].length).setValues(values);
// Step 4
// sheet is still the "TODAY" sheet
let range = sheet.getRange("C12");
range.setFormula(
'=IFERROR(IFERROR(IFS(VLOOKUP($B12,INDIRECT'
+'('
+'"'
+'\''
+'"'
+'&$Q$4&'
+'"'
+'\''
+'!"&"!"&"A1:O2000")'
+',15,false)="D","D",$N12="Quote","Q",$N12="Important","I",$N12="On Hold","H",$N12="IN TRANSIT","T",$N12="REQUEST","R",$N12="INCOMPLETE","N",$N12="COMMENT","C"),VLOOKUP($N12,$B$3:$C$9,2,FALSE)),"")');
// Pastes the above formula into cell C12.
let fillRange = sheet.getRange(12,3,values.length,1);
range.copyTo(fillRange);
sheet.activate();
}
catch(err) {
console.log(err);
}
}

How to create a function that triggers whenever a new sheet is created? [duplicate]

This question already has an answer here:
How to set sheet create event as a trigger in apps script
(1 answer)
Closed 1 year ago.
I have a spreadsheet with a sheet of modification times for each of my other sheets. For example, I have a 'Signing' and 'Profile' sheet, and in my modifications times sheet I have:
Sheet Name
Modification Time
Signing
1639335205000
Profile
1639335207338
I want to create a function that, whenever I create another sheet, automatically adds it to the modifications times sheet as a new row.
I have looked at ScriptApp triggers and events but haven't found anything that is related(onEdit for example might be useful if there was a way to know if the edit was creating a sheet (if it even catches those events) but would also be triggered all the time).
I've actually wondered this myself and I hope someone can post a better answer than my method.
First, I create a named range that's going to store the number of sheets in the workbook (in below code I used sheetCount). Make sure this is at the workbook level (which by default google does).
Then leverage onEdit with this:
function onEdit(e) {
//var range = e.range;
const ss = SpreadsheetApp.getActiveSpreadsheet();
const storedSheetCount = ss.getRange("sheetCount")//<-- need to setup named range
var sCount = ss.getNumSheets();
if(storedSheetCount.getValue()!=sCount){
if(storedSheetCount.getValue()<sCount){
// more
Browser.msgBox("You addeded a spreadsheet!")
}else{
//for less
Browser.msgBox("you took one away")
}
//both cases update the value
storedSheetCount.setValue(sCount); //<--- updates stored count
}
}
I am well aware that this is not ideal for a variety of reasons including:
No code is executed until an edit ACTUALLY happens.
Stated differently, adding a sheet is not an edit event, so a user must then click into a cell or delete a blank one to kickoff the procedure.
Takes up space on the spreadsheet front end.
I hate helper columns/cells. Names is one area that Excel definitely crushes GoogleShhets as it allows direct references to values. Thus with Excel, I could avoid cluttering a spreadsheet by setting a named rage to the sheetCount (ie. refersTo:=3). One alternative to this would be to use the spreadsheet's file description, but this requires granting permissions to Drive Service which opens up all kinds of security risks for such a trivial request.
If anyone can do better, please share.

how do I reset the google sheets counter?

Current function: Using apps script, I delete a few tabs then add a few tabs. So new a new sheet is like 'Sheet 467'. The number is getting big very quickly even though there are only 8 sheets in the file.
Desired function: I'd like to reset the sheet counter so the next sheet is 9 (number of sheets currently in the file +1) rather than 468 (number of sheets there have ever been in the file +1).
I tried to google this but I think the search terms are too generic. I couldn't find it anywhere.
Explanation
Unfortunately, this is the default behaviour of how sheets are created. You can request it as a feature but I don't think this will ever be implemented.
However, I would like to propose the following two workarounds:
User inserts a new sheet:
Take advantage of the onChange trigger and especially the event
object property changeType. When you add a new sheet, the latter
takes the value of INSERT_GRID.
This allows you to trigger a piece
of code when a new sheet is added by the user.
The following script will check whether you created a new sheet. If you did, it will rename the last created sheet as Sheet plus the total number of sheets.
Script inserts a new sheet:
If the new sheets are created by a script the onChange trigger can't be used. Then use the second solution I propose.
Workarounds:
Solution when the user inserts a new sheet:
function nameNewSheet(e) {
if (e.changeType == 'INSERT_GRID') {
const sheets = SpreadsheetApp.getActive().getSheets();
sheets[sheets.length-1].setName(`Sheet${sheets.length}`)
}
}
In order to use this solution you have to create an installable onChange trigger for the nameNewSheet function. Please read this answer on how to do that.
Solution when the script inserts a new sheet:
If the script itself adds new sheets, then onChange won't be triggered. But you can incorporate the following two lines into your existing script that inserts new sheets in order to rename the last created sheet.
function myFunction() {
//
// the code of the script that adds a new sheet
//
const sheets = SpreadsheetApp.getActive().getSheets();
sheets[sheets.length-1].setName(`Sheet${sheets.length}`);
}
Illustration:

Script in Google Apps Script that copies values in a range to another sheet

I am working on a tool that will allow my company to track its financial investments.
I need to create a simple data entry form for users to input transaction data, which will then populate a master sheet which is the basis for the analyses the tool does. I cannot do this via Google Forms because the data entry form uses a lot of conditional formatting based on other data in the sheet.
I have uploaded a very simplified sheet to illustrate what I need: this
What I am looking for is a script that, upon clicking Submit in the Data Entry sheet, copies the values (NOT the formulas) in B12:E12 to the first empty row (in this case, row 8) in the "Master Table" sheet. Ideally, clicking "Submit" will also clear the data entry fields in C4:C7 in the "Data Entry" sheet.
I have looked through various forums for a solution but have not found anything that does exactly that. I am sorry to say I am a complete newbie at Google Apps Script, therefore I could not write my own code to share, which I am aware is customary when asking a question here.
If anyone could point me in the right direction regardless, it would be much appreciated. I am currently trying to learn JavaScript and Google Apps Script using online resources, but for this specific project, it would take too long for me to reach a level where I could help myself.
Thank you very much, GEOWill!
Your answer solved my problem and thanks to your comments, I was able to understand exactly what your code does. I changed the code only to remove the Menu (but thank you very much for showing me how that is done anyway) - I tied the function to a Submit button inserted as a drawing. I also added some code to clear the contents of the entry range after clicking the button (this was suggested by someone else).
The final code that I used is:
var ss = SpreadsheetApp.getActiveSpreadsheet();
var entry_sheet = ss.getSheetByName("Data Entry");
var master_sheet = ss.getSheetByName("Master Table");
function mySubmit() {
var entry_range = entry_sheet.getRange("B12:E12")
var val = entry_sheet.getRange("B12:E12").getDisplayValues().reduce(function (a, b) {
return a.concat(b);
});
Logger.log(val);
master_sheet.appendRow(val);
entry_sheet.getRange("C4:C7").clearContent()
}
I hope this helps others with a similar problem! Love how supportive this community is. Thanks for helping out.
I would begin by using a menu function to run your code in (rather than a cell button). You could use a cell button, but I believe you need to insert an image and assign a javascript function to that image anyways.
Basically, you begin by going to tools, script editor. Create an onOpen function and create a trigger that runs the onOpen() function each time the spreadsheet is opened. Inside the onOpen() function, we create a menu into which a physical menu item (a kind of button) exists (called 'Submit'). Finally, we associate a function to this button (I called it mySubmit()).
Inside of the mySubmit() function is where most of the functionality you are looking for exists. At this point, it is just a matter of copying from one range of cells and pasting them to another range of cells. For this, you'll notice that I had to setup a few variables ahead of time (ss, entry_sheet, master_sheet, entry_range, and master_row).
One last thing, you may want to protect the Master table sheet because if someone accidentally edits a cell beyond the last one edited, the input row would be copied to that row (due to how the getLastRow() function operates).
Hope this helps!
var ss = SpreadsheetApp.getActiveSpreadsheet();
var entry_sheet = ss.getSheetByName("Data Entry");
var master_sheet = ss.getSheetByName("Master Table");
function onOpen() {
SpreadsheetApp.getUi()
.createMenu('MyMenu')
.addItem('Submit', 'mySubmit')
.addToUi();
}
function mySubmit() {
var entry_range = entry_sheet.getRange("B12:E12");
var master_row = (master_sheet.getLastRow() + 1);
entry_range.copyValuesToRange(master_sheet, 1, 3, master_row, master_row);
}

CopyTo: How to push data/copy a cell from one google spreadsheet to another google spreadsheet using google apps script?

I look for a solution to copy a specific cell value in the Source spreadsheet from tab "Sum all" to another Spreadsheet to the Target Spreadsheet Tab "Copy all". It should work every time i change the Value of Cell G10. Access to the Target sheet is granted before i enter any Value to G10.
(Source Tab Name is "Sum all:G10" - Sheet has 10 different Tabs)
(Target Tab Name is "CopyData:T12" - Sheet has 10 different Tabs)
Easy way
Use the built-in IMPORTRANGE() function in Google Apps:
In your Target cell, type the following formula
=IMPORTRANGE("FILE_ID_HERE","Sum all!G10:G10")
The syntax for this function is
=IMPORTRANGE("FILE_ID","SHEET_NAME!RANGE_START:RANGE_END")
When you first type in this function, you'll get an error in the cell. Simply click on it and select "Allow" to link the two sheets together. This error will occur even if it is the same spreadsheet. This function can link two separate spreadsheets, too, as long as you have edit access to both.
Hard Way
I'm assuming from your question that you want to copy values to and from the same spreadsheet document, but to different cells that are located on different sheets of the spreadsheet. The Google Apps Script API calls tabs "sheets" and the overall document "spreadsheet".
First, open the script editor
Open your spreadsheet that you'd like to make this script for.
Select "Tools" in the toolbar, then "Script Editor"
Second, make a function for onEdit.
Making a function named onEdit will create a function that runs every time the edit trigger is fired, using a no-authorization "simple trigger". Google Sheets automatically sends this event every time a cell is edited by a user. The argument e for the function is the event passed by the trigger.
function onEdit(e) {
// Get the sheet named "Sum all" from the active spreadsheet (i.e. the one you are editing)
var source = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Sum all");
// Returns the active cell
var cell = source .getActiveCell();
// Compare to see if its the right cell you're looking for
// getRow and getColumn methods return integers for the row and column of the cell
// A = 1, B = 2, ... G = 7
if (cell.getRow() == 10 && cell.getColumn() == 7) {
// If its the right cell, copy to the other cell
var target = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("CopyData");
// set the value of the desired cell in the target sheet
target.getRange("T12").setValue(cell.getValue());
}
}
Third, save the script
Save the script, reload the file, and test it out.
If your tabs are on different spreadsheets
Change this line:
var target = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("CopyData");
to this:
var target = SpreadsheetApp.openById("FILE_ID").getSheetByName("CopyData");
and insert the file ID for the target spreadsheet where I've written FILE_ID.
You will also need to use an "installed trigger", since a simple trigger cannot open a remote spreadsheet. To do this, change the name (so it is no longer a simple trigger function), and follow the steps here