Is there a way on to run a script whenever the user switches between sheets in a Google Sheets Spreadsheet?
More or less like onOpen, but instead of running when the document is opened, it should fire every time the user switches to another sheet.
Here is my work-around for onSheetChange:
function saveActiveSheet() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var actsheet = ss.getActiveSheet();
// The onSelectionChange() function executes in a separate thread
// which does not use any script global variables, so use the
// PropertiesService to maintain the user global state.
var userProperties = PropertiesService.getUserProperties();
userProperties.setProperty('ACTIVE_SHEET', actsheet.getSheetName());
}
function onSheetChange(e) {
// Do anything needed after a new sheet/tab selection
}
function onSelectionChange(e) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
// Get current sheet name and compare to previously saved sheet
var currentactsheet = ss.getActiveSheet();
var currentactsheetname = currentactsheet.getSheetName();
var userProperties = PropertiesService.getUserProperties();
var actsheetname = userProperties.getProperty('ACTIVE_SHEET');
if (currentactsheetname !== actsheetname) { // New sheet selected
saveActiveSheet();
onSheetChange(e); // Call custom sheet change trigger
}
// Do anything needed when a different range is selected on the same sheet
else {
var range = e.range;
}
}
function onOpen(e) {
saveActiveSheet();
}
UPDATE: On April 2020 Google added onSelectionChange(e) which could be used to check if the user switched between sheets aka tabs.
At this time there isn't a trigger related to switch for one sheet to another. To learn about the available triggers in Google Apps Script, please checkout Triggers and events - Google Apps Script Guides
As a workaround you could a custom menu to and SpreadsheetApp.setActivesheet to switch between sheets by using a script instead of using tabs and including in that script a call to the function to be run when switching from one sheet to another.
I have created a "Feature request" to implement "onSheetChange" trigger at google issue tracker. You can star it, so google now you want this:
https://issuetracker.google.com/issues/72140210
And here is related question with possible workaround: How to capture change tab event in Google Spreadsheet?
you can use
sheet1.showSheet();
sheet2.hideSheet();
Related
I would like a user to be able to click a button and have their view change to a different sheet.
I have looked through all of the documentation but I can't find any methods that actually cause a UI change. For example getActiveSheet(), setActiveSheet() or setActiveRange() or activate() only change what the focus of the code not what the user can see.
There is a "duplicate" question here: In Google Sheets, how do you change the sheet that the user sees?
but using setActiveSheet() does not result in any changes.
function ChangeSheet(){
var ss = SpreadsheetApp.openById("exampleID");
var MainBoardSheet = ss.getSheetByName('Main board');
ss.setActiveSheet(MainBoardSheet);
}
I have tried a number of variations including setting the active sheet by name and selecting ranges from that sheet but nothing seems to change the view I have.
The functions like setActiveSheet(), setActiveRange(), activateAsCurrentCell() will change the UI view, but only within the active user session in which the script is running, so you must use Spreadsheet.getActiveSpreadsheet(); rather than openById().
You can use these functions from a custom menu or onEdit trigger to change the active sheet in the UI from which they were triggered, but it won't impact other open copies of the sheet, for this or other users.
If you are attempting to trigger via another method, where the code isn't executing in the context of the current users session, you won't see any impact on the UI side.
/* this does not work */
function getByIDJumpByActive(){
var spreadsheet= SpreadsheetApp.openById('1u3d8Auoi3CJNqZgLiui2oKIB3FDYOwAHLq59zWY5fNk');
var sheet = spreadsheet.getSheetByName('Sheet3');
spreadsheet.setActiveSheet(sheet);
spreadsheet.toast('opened by id and jumped by active sheet');
}
/* the below all work within the active user session. */
function jumpByRange() {
var spreadsheet= SpreadsheetApp.getActiveSpreadsheet();
var range = spreadsheet.getRange('Sheet2!A1');
range.activateAsCurrentCell();
spreadsheet.toast('jumped by active cell');
}
function jumpByActive(){
var spreadsheet= SpreadsheetApp.getActiveSpreadsheet();
var sheet = spreadsheet.getSheetByName('Sheet3');
spreadsheet.setActiveSheet(sheet);
spreadsheet.toast('jumped by active sheet');
}
function onOpen(){
var doc = SpreadsheetApp.getUi();
var menu = doc.createMenu('Switch');
menu.addItem('by range','jumpByRange');
menu.addItem('by active','jumpByActive');
menu.addItem('get by ID and jump by active','getByIDJumpByActive');
menu.addToUi();
}
function onEdit(){
jumpByActive();
}
I'm pretty sure the RANGE.activateAsCurrentCell() method will bring the user there even if the range is in another Sheet:
https://developers.google.com/apps-script/reference/spreadsheet/range#activateAsCurrentCell()
In your Spreadsheet document go:
Tools -> Script editor
Then you will have an apps script editor where you can put this code and it will change the sheet as you wanted:
function ChangeSheet(){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var changeSheet = ss.getSheetByName('Main board');
ss.setActiveSheet(changeSheet);
}
Let me know if it works for you
I am trying to get a Google Apps Script to trigger on edit of a particular cell. Right now I have achieved this using a run-time trigger option built in Google Sheets. However, I lose the run-time trigger when I make a copy.
Hence, I have managed to find a programmable trigger from various sources and patched something together, but it is not working. I have no programming knowledge, so I am unable to understand where I am going wrong.
My objective is to run the script when user edits the Named Range "monthfilter".
With script trigger creation
function HideColumns() {
//open the current spreadsheet and get the value of the named range and trigger project
var ss = SpreadsheetApp.getActive();
ScriptApp.newTrigger("monthfilter").forSpreadsheet(ss).onedit()
var name = ss.getRangeByName("monthfilter");
var namevalue = name.getValue();
My previous function:
function HideColumns() {
//open the current spreadsheet and get the value of the named range
var ss = SpreadsheetApp.getActive();
var name = ss.getRangeByName("monthfilter");
var namevalue = name.getValue();
//show or hide February
if (namevalue == "February") {
var sheet = ss.getSheetByName("MIS");
sheet.hideColumns(30);
/** other stuff */
Not sure what is the 'runtime trigger' you mean, there are three kinds of trigger:
Simple trigger
Installable trigger
Time driven trigger
onEdit(e) is just a Simple trigger.
Let's take a look to the following situation:
yourMainSpreadsheet - (You are the owner)
|
binding script functions: - 1. function makeCopy( ) {...}
2. function monthFilter(e) {...}
3. function onEdit(e) { monthFilter(e); }
|
| After copying, new spreadsheet remains all functions and simple trigger.
V
newSpreadsheet- (You are the owner, maybe share with other editors.)
|
binding script functions: - 1. function makeCopy() {...}
2. function monthFilter(e) {...}
3. function onEdit(e) { monthFilter(e); }
No matter what kind of trigger you use or just manually run makeCopy() in yourMainSpreadsheet, onEdit(e) would be copied to new spreadsheet too, and it works when you or other editors make edits, you don't need to do anything:
function makeCopy(){
var mainSpreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var newSpreadsheet = mainSpreadsheet.copy('newSheet');
}
function monthFilter(e) {
var thisSheet = e.source.getActiveSheet();
var name = thisSheet.getRangeByName("rangeMonthfilter");
var nameValue = name.getValue();
//...
}
function onEdit(e) {
monthFilter(e);
}
What you have done is set a installable trigger to new spreadsheet.
Highlight of documentation about difference between them.
Installable triggers, however, offer more flexibility than simple
triggers: they can call services that require authorization ...
For example:
function monthFilter(e) {
var thisSheet = e.source.getActiveSheet();
var name = thisSheet.getRangeByName("rangeMonthfilter");
var nameValue = name.getValue();
// If you have operation like:
var onlyYouHavePermission = SpreadsheetApp.openById('xxxxxxxxx');
/* This is not allowed if you just use Simpler trigger onEdit(e) to
drive monthFilter(). */
}
For this, now is the time to use installable trigger:
function makeCopy(){
var mainSpreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var newSpreadsheet = mainSpreadsheet.copy('newSheet');
ScriptApp.newTrigger('monthFilter')
.forSpreadsheet(newSpreadsheet)
.onEdit()
.create();
/* The host of this trigger is you, it's like you
deploy a trigger to the newSpreadsheet, and make other editors
available to do something on "onlyYouHavePermission" through it.*/
}
Update
If all you want to do is just in HideColumns() you provide, you just need to add a new function which is a simpler trigger in script editor:
function onEdit(e) {
var ss = SpreadsheetApp.getActive();
var name = ss.getRangeByName("monthfilter");
var namevalue = name.getValue();
if (namevalue == "February") {
/* If you copy the spreadsheet, of course including the sheets
inside it, and you share the permission of the new spreadsheet to
other editors, because they own the permission, authorization
is not required here.
*/
var sheet = ss.getSheetByName("MIS");
sheet.hideColumns(30);
}
}
I have written a custom function that calls the code below to create a temporary sheet. When I ran it in Google Editor I was asked for authorisation and granted access.
My custom function runs well in Google Editor but doesnt work in the spreadsheet and I get the error "You do not have the permission to call insertSheet".
I created the spreadsheet and custom function so I own (have access) to everything.
var tempSheetName = "temp";
var sp = SpreadsheetApp.getActiveSpreadsheet();
var activeCell = sp.getActiveCell();
sp.insertSheet(tempSheetName);
From Google Apps Guide : Link
If your custom function throws the error message You do not have
permission to call X service., the service requires user authorization
and thus cannot be used in a custom function.
You may have your reasons, why you want to accomplish it in this way. But I'm really not sure if a custom function in a cell is the right place to create a new tab. You usually do calculations within a cell not document manipulations.
What, if you'd create a menu entry for the function like this and call it from there?
function onOpen() {
// instantiate UI object
var ui = SpreadsheetApp.getUi();
// create the menu
ui.createMenu("Functions")
.addItem("Create Sheet", "createSheet")
.addToUi();
}
function createSheet() {
// get active spreadsheet
var ss = SpreadsheetApp.getActiveSpreadsheet(),
sheetName = 'mySheet',
sheet = ss.getSheetByName(sheetName);
// delete sheet if it already exists
if (sheet) ss.deleteSheet(sheet);
// insert sheet
ss.insertSheet(sheetName, 0);
}
Do you still get an error? Just curious...
I want my spreadsheet to open at a certain sheet. I've tried the following but it still just opens the first sheet.
function setActiveSheetToday(){
var date = new Date();
var sheet = getSheetByDate(date); //custom function that returns sheet object
var SS = sheet.getParent();
SS.setActiveSheet(sheet);
}
Then I make an onOpen trigger.
ScriptApp.newTrigger('setActiveSheetToday').forSpreadsheet(SS_ID).onOpen().create();
When I open the spreadsheet, it still opens at the first sheet. Any suggestions?
EDIT
I think I found the reason why it doesn't work here: https://developers.google.com/apps-script/guides/bound#special_methods
So it apparently only works for bounded scripts..
You could make the sheet you want to open on the first sheet. Just add SS.moveActiveSheet(1); to your setActiveSheetToday() function.
Alternatively, activating a sheet from a bound script changes the focus as you want. Doing it from a separate script file doesn't appear to.
Have you checked the object, your sheet?
You may try this function for simple testing:
function onOpen() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName('Sheet1');
ss.setActiveSheet(sheet);
}
make sheet called "Sheet1", go to any other sheet and reload the page.
for your code I suggest the test:
Logger.log(sheet.getName());
I'm trying to write a script that display row value in a SpreadSheet file using UiApp.
And this script will need to update the data whenever there is a change to the SpreadSheet. This would normally be simple with just Trigger onChange.
However, the SpreadSheet itself is also updated by another function, and it seem that onChange is not triggered when the changes made are caused by another AppScript.
Is there an alternative way to achieve what I described?
Code below, thanks
var ss = SpreadsheetApp.openById("tqwJk280I1O_yW5oNX9nLQA");
function doGet() {
var app = UiApp.createApplication();
var masPanel = app.createFlowPanel().setId("panel");
var number = parseInt(ss.getRange("A1").getValue());
masPanel.add(app.createLabel(number));
masPanel.add(app.createFlowPanel().add(app.createFormPanel()
.add(app.createFlowPanel()
.add(app.createSubmitButton("Plus!")))));
app.add(masPanel);
return app;
}
function doPost()
{
var num = parseInt(ss.getRange("A1").getValue()) + 1;
ss.getRange("A1").setValue(num);
}
function onChange()
{
ss.getRange("A2").setValue("hahaha");
var app = UiApp.createApplication();
app.remove(app.getElementById("panel"))
var masPanel = app.createFlowPanel().setId("panel");
var number = parseInt(ss.getRange("A1").getValue());
masPanel.add(app.createLabel(number));
masPanel.add(app.createFlowPanel().add(app.createFormPanel()
.add(app.createFlowPanel()
.add(app.createSubmitButton("Plus!")))));
app.add(masPanel);
return app;
}
The onChange() function will not have reference to the UI. For example, if there are multiple people who have the same UI open, which one should be updated ?
So, the solution to your problem is for your UI code to poll the spreadsheet regularly and update the UI if the spreadsheet has changed. To do so, you can use the undocumented addTimer() method.
See this SO question & answer for a nice example