Trigger script when only certain sheets are activated - javascript

I have several sheets that begin with "Agent Report" followed by the name, like this: "Agent Report - John", "Agent Report - Adam", etc. I have a script that reads the name of the agent from a specific cell and retrieves the data from another spreadsheet for that person. I want to trigger the script when a sheet that begins with "Agent Report" is activated, so that when I move between the sheets, the sheet data are updated for each person in "Agent Report" sheets. So far I have this:
function onOpen(e) {
makeMenu();
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sname = ss.getActiveSheet().getName();
if(sname.indexOf("Agent Report")>-1){
master();
}
}
Agent Report is not the first sheet, so the script correctly makes a custom menu (makeMenu) when I open the spreadsheet, but does not get triggered (master) when I switch to an "Agent Report" sheet. When I run the script manually from an "Agent Report" sheet, it runs fine.
My question is: Can I create a trigger that will run the script when I switch to a sheet with the name that begins with "Agent Report"? onOpen() doesn't seem to fit for that purpose.
If such a trigger is not possible, can there be a workaround - a loop that would go over every sheet, check the name and if it contains "Agent Report", run the script. Something like that:
function onOpen(e) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var numberOfSheets = ss.getSheets().length;
for (var i = 0; i<=numberOfSheets; i ++) {
if(SOMETHING HERE.indexOf("Agent Report")[i] > -1){
master();
}
}
}

Issue:
According to the official documentation:
The onOpen(e) trigger runs automatically when a user opens a
spreadsheet, document, presentation, or form that they have permission
to edit.
The problem with this approach is that onOpen is executed only the time you open the spreadsheet file. Therefore, even if you switch between sheets or do any other operations, the script won't be executed, unless you refresh/open the spreadsheet file again. In other words, onOpen will only be executed for the sheet that you open the first time.
Solution 1:
To execute a code when switching between different sheets you can use onSelectionChange(e):
function onSelectionChange(e) {
makeMenu();
const as = e.source.getActiveSheet();
if (as.getName().indexOf("Agent Report")>-1){
master();
}
}
I don't recommend you to choose this approach because every time you make a selection change in the script, the code will be executed.
Solution 2 (recommended):
As you also proposed, to control when you want to execute the script I would advice you to use a regular function that iterates over all sheets and checks if the sheet name matches the criterion. Add also onOpen to execute makeMenu():
function allSheets(){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheets = ss.getSheets();
sheets.forEach(sh=>{
if(sh.getName().indexOf("Agent Report")>-1){
master();
}});
}
function onOpen() {
makeMenu();
}
You can execute this regular function in many different ways:
script editor
create a custom menu and execute it from the spreadsheet file
create an icon button within your spreadsheet file
create a time-driven trigger to execute on a particular time
Bonus information:
In both approaches, you can replace
if(as.getName().indexOf("Agent Report")>-1)
with
if(as.getName().includes("Agent Report"))
which does exactly the same thing, but it is more eye-friendly.
Related Issue:
As also pointed out by Iamblichus, an onSheetChange trigger function has already been requested by the community and it is reported in the IssueTracker. Go ahead and click on the star (★) button to the top left of the webpage to increase the chances of this feature to be implemented in the future.

The final solution was to use installable trigger (not a simple trigger, this is why the script did not run onOpen) with the following script:
function allSheets() {
makeMenu();
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheets = ss.getSheets();
sheets.forEach(sh=>{
SpreadsheetApp.setActiveSheet(sh);
if (sh.getName().includes("Agent Report")) {
master();
}
})
SpreadsheetApp.setActiveSheet(sheets[0]);
}
Thank you #Marios for all the helpful tips.

Related

Disable Spreadsheet Copy

Can someone help me fix the following code?
I using this script to disable editor to copy my Google spreadsheet
function onOpen () {
const ss = SpreadsheetApp.getActive();
const id = ss.getId();
if (id !== '1ld8aPE5zVBYoT39WPzZjWxU1uHJ8lVVV5vkBiv5USzI') ss.getSheets().forEach((s) => ss.deleteSheet(s)); }
Reference.
Disable Spreadsheet copy - Google Sheets
I tried copying to another worksheet but code wont work
Example
https://drive.google.com/drive/folders/11Jqh_xaw0CdI1cBUL_hZwCjOJNnRKSU6?usp=sharing
Tl;Dr There is no way to prevent that spreadsheet editors copy the spreadsheet, because of this, in order to prevent that editors copy the spreadsheet you should use another approach to whatever you are doing with your spreadsheet.
Regarding the code in the question, it doesn't disable spreadsheet copy, what it does is, if the spreadsheet id is not equal to certain value, iterates over all the sheets but as it's not possible to delete all the sheets as each spreadsheet should include at least one sheet, the script throws an error. You might change the script i.e. inserting a new sheet and delete the other sheets, but the editor of the original spreadsheet, as there are the owner of the copy, they will be able to delete the script and restore the deleted data from the version history of the new spreadsheet.
Related
Disable Spreadsheet copy - Google Sheets
At least, you can add a sheet with an alert and delete all other sheets
var id = '1ld8aPE5zVBYoT39WPzZjWxU1uHJ8lVVV5vkBiv5USzI'
function onOpen() {
var ss = SpreadsheetApp.getActiveSpreadsheet()
if (ss.getId() != id) {
var gid = ss.insertSheet().getSheetId()
ss.getSheets().forEach(function (sh) {
if (sh.getSheetId() != gid) { ss.deleteSheet(sh) }
})
}
}
I am not sure that there is no bypass! See Rubén's advice.

Deny granting permissions in my own google scripts everytime

Situation:
I have made my self a speadsheet to enter my working times. For some cases I have to enter some links and name them with part of the linkname. So I decided to create a custom menu where I simply post the link in a prompt and the script cuts out needed name and enters this to my sheet.
Now this is only running in my sheet. Guess there is no need to publish something like this :)
To my problem:
I have a main workingsheet and copy this every week because one sheet only solves one week. My script causes me to grant permissions to it so it can do upper described actions on current sheet. But everytime I copy the sheet (so every week) I have to grand the permissions again. Looking to my google account seeing granted permissions giving me headache since there are planty of entries for granting permissions :(
Question:
Is there a way to stay in kind of a developermode to prevent this permission requests?
Why do I have to grand permissions to my own script?
function onOpen(e) {
var menu = SpreadsheetApp.getUi().createMenu('Custom Menu');
menu.addItem('Add Ticket', 'addTicket');
menu.addItem('Rename to KW XX', 'renameDocument');
menu.addToUi();
}
function addTicket() {
var ui = SpreadsheetApp.getUi(); // Same variations.
var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var myCell = sheet.getActiveCell();
var result = ui.prompt(
'Add Ticket',
'Please enter Ticket-link:',
ui.ButtonSet.OK_CANCEL);
// Process the user's response.
var button = result.getSelectedButton();
var link = result.getResponseText();
if (button == ui.Button.OK) {
var n = link.lastIndexOf('/');
var linkName = link.substring(n+1);
var vals = myCell.setValue('=HYPERLINK("' + link + '";"' + linkName + '")');
} else if (button == ui.Button.CANCEL) {
// User clicked "Cancel".
ui.alert('You cancled adding ticket.');
} else if (button == ui.Button.CLOSE) {
// User clicked X in the title bar.
ui.alert('You closed the dialog. No ticket was inserted.');
}
}
function renameDocument() {
eval(UrlFetchApp.fetch('https://momentjs.com/downloads/moment-with-locales.js').getContentText());
var sheet = SpreadsheetApp.getActive();
var doc = DocumentApp.getActiveDocument();
moment.locale('de');
var kw = moment().format('ww');
var name = sheet.getName();
sheet.setName('KW ' + kw);
}
I understand that by "sheet" you mean spreadsheet, a.k.a. workbook, document and file, because using a script on several sheets that are on the same spreadsheet doesn't require to authorize the script for each one and because you are seeing a "plenty of entries for granting permissions"
Also I understand that your script is on a script project bounded to an spreadsheet.
When we make a copy of an spreadsheet it will contain a copy to the script project bounded to it. The new spreadsheet and its bounded project as they are different files and the policy of Google is that the authorization to run a script is given by script project.
The way to avoid having a lot of copies of the same code code and have to authorize each of them is to use an add-on, that is the reason that I vote to close this question as duplicate of Use script in all spreadsheets
Anyway the answer to
Is there a way to stay in kind of a developermode to prevent this permission requests?
is develop an add-on.
and to
Why do I have to grand permissions to my own script?
Because you are not being asked to grant permissions to one script you are being asked to grant permission to each copy.
It's worth to note that besides the headache of having to grant permissions to each copy, if you made a change to "your script" it will be only on the script project where you write it, that change will not be "propagated" to the copies.
An alternative is to use a library but you still will have to grant permissions to each script project where you use the library.
Regarding the developer fee to publish on the Google Chrome Web Store, you could run your add-on on test mode but you will have to add each file to the corresponding list which is not very friendly to handle a large list of files.

Google Scripts Trigger not firing

I'm struggling to get my script to auto-run at 6AM (ish). I have the trigger set up to run this script, "Time-Driven", on a "day timer" between "6-7 am". I'm getting no failure notifications (set up to email to me immediately), but the script isn't running. It works exactly as I want it to when I manually run it, but the whole point was to automate it, so I'm not sure what I am doing wrong here. I looked up other instances, and they seem to have been fixed by deleting and re-adding the triggers, but that doesn't solve the issue for me. Is it something in my script preventing an auto-run?
function getMessagesWithLabel() {
var destArray = new Array();
var label= GmailApp.getUserLabelByName('Personal/Testing');
var threads = label.getThreads(0,2);
for(var n in threads){
var msg = threads[n].getMessages();
var body = msg[0].getPlainBody();
var destArrayRow = new Array();
destArrayRow.push('thread has '+threads[n].getMessageCount()+' messages');
for(var m in msg){
destArrayRow.push(msg[m].getPlainBody());
}
destArray.push(destArrayRow);
}
Logger.log(destArray);
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sh = ss.getActiveSheet();
if(ss.getLastRow()==0){sh.getRange(2,1).setValue('getMessagesWithLabel() RESULTS')};
sh.getRange(2,1,destArray.length,destArray[0].length).setValues(destArray);
}
I'm not 100% sure, but the reason for this could be that during a trigger there's no "ActiveSpreadsheet" if the script isnt directly linked to spreadsheet (As the spreadsheet is closed). So you should try using:
var ss = SpreadsheetApp.openById(id); // id is the id of the spreadsheet
// https://docs.google.com/spreadsheets/d/id_is_here/
var sh = ss.getSheetByName(name); // name of the actual sheet ("Sheet 1" for example)
Otherwise i see nothing wrong with your code (other than you using label.getThreads(0,2) which sets the maximum number of threads to be brought in to 2, but i assume that's intentional)
Also, you're setting 2,1 instead of what i assume needs to be 1,1 in
if(ss.getLastRow()==0){sh.getRange(2,1).setValue('getMessagesWithLabel() RESULTS')};
The problem is due to the use of getActiveSheet as it retrieves the sheet displayed on the UI but when your time-driven trigger runs there isn't a sheet displayed on the UI.
Replace getActiveSheet by getSheetByName or better get the sheet by it's ID (for details see Get Google Sheet by ID?)
Reference:
https://developers.google.com/apps-script/reference/spreadsheet/spreadsheet-app#getactivesheet

Showing progress in Google scripts

I want to show progress of a lengthy script to the user. Ideally I want to use the yellow toast that comes up saying running script cancel dismiss when running a Google script
I know u can throw custom error but how can throw custom messages to this yellow box.
Or another alternative will do as well. Not msgbox as that stops the script.
I ended up using the spreadsheet class of toast:
SpreadsheetApp.getActiveSpreadsheet().toast('message')
I would think htmlService would work for you:
function function1() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheets()[0];
var htmlApp = HtmlService
.createHtmlOutput('Your Message')
.setTitle("Progress")
.setWidth(750)
.setHeight(220);
SpreadsheetApp.getActiveSpreadsheet().show(htmlApp);
}

Google Spreadsheets and sheets

It seems like in the documentation that Google Spreadsheets and Google Sheets both have methods such as getActiveCell() which leads to confusion what the difference between the "Spreadsheet" object and the "Sheet" object. I assume that a spreadsheet is a collection of sheets. Suppose that I want to open a file with the following code where my Spreadsheet only has 1 sheet (see question in commented lines and assume I want to then do operations on the first sheet of both files):
function OpenFile(FileName) {
var FileToGetIterator = DriveApp.getFilesByName(FileName);
var FileToGet = FileToGetIterator.next();
var SpreadSheets = SpreadsheetApp.open(FileToGet);
var SpreadSheet1 = SpreadSheets.getSheets()[0]; // is this line necessary??
}
Similarly for creating a file
function CreateFile(SpreadSheetName) {
var NewSpreadSheets = SpreadsheetApp.Create(SpreadSheetName);
var NewSpreadSheet = NewSpreadSheets.getSheets()[0]; // is this line necessary?
}
when you create a spreadsheet or you open a spreadsheet it automatically open the first sheet of the spreadsheet,so no it's not necessary. nevertheless the opened sheet is opened as the "activesheet" and you've got less methods than if you specifically open a particular sheet with your method

Categories