Switching active sheet after "On Change" trigger - google-apps-script

So I have an "on change" trigger set to activate this function so that it pulls the name of the tab that has just been created and puts it in a message box. Unfortunately when this is triggered after a new tab has been created it always treats the first tab in the spreadsheet as the active tab (i.e. 'Sheet1). Any ideas on how to to get it to switch the active sheet to the newly created tab?
function mynewFunction(e) {
if (e.changeType == 'INSERT_GRID') {
var ss = SpreadsheetApp.getActiveSheet().getName();
Browser.msgBox(ss);
}
}

The last sheet added via UI will be the last one, so you could get it with something like SpreadsheetApp.getActiveSpreadsheet().getSheets().pop().
You could then make it active with setActiveSheet():
function mynewFunction(e) {
if (e.changeType == 'INSERT_GRID') {
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var lastSheet = spreadsheet.getSheets().pop();
spreadsheet.setActiveSheet(lastSheet);
Browser.msgBox(spreadsheet.getActiveSheet().getName());
}
}

Managed to figure out a workaround using by combining the use of script Properties, the "On Open" trigger, and the "On Change" trigger!
//Gets current list of sheets on open
function onOpened() {
var currentSheets = new Array();
var sheets = SpreadsheetApp.getActiveSpreadsheet().getSheets();
for (var i=0 ; i<sheets.length ; i++) currentSheets.push(sheets[i].getName())
var currentSheets = currentSheets.join();
var updateProperties = PropertiesService.getScriptProperties();
updateProperties.setProperty('sheetsMaster', currentSheets);
}
//Displays name of new sheet on change
function onChanged(e) {
if (e.changeType == 'INSERT_GRID') {
var scriptProperties = PropertiesService.getScriptProperties().getProperties();
var sheetsMaster = scriptProperties.sheetsMaster;
var oldList = sheetsMaster.split(",");
var newList = new Array();
var changedSheets = SpreadsheetApp.getActiveSpreadsheet().getSheets();
for (var i=0 ; i<changedSheets.length ; i++) newList.push(changedSheets[i].getName())
for (var i=0 ; i<newList.length ; i++){
if (newList[i] != oldList[i]) {
//Displays newly added sheet name
Browser.msgBox(newList[i]);
//Updates list to reflect change
oldList.splice(i,0,newList[i]);
var setNewProperties = PropertiesService.getScriptProperties();
setNewProperties.setProperty('sheetsMaster', oldList.join());
return;
}
}
}
}

Related

How to allow non-sheet owners run scripts that involve protected cells

I have the script below where some cells are protected because they contain formula but I can script linked to buttons that when executed, it updates the cell values in these protected cells, this is fine if you are the sheet owner but if you are not you get a error saying 'You are editing protected cells....'
I have seen some solutions where the script has been deployed as a web app and then set so it always runs as the owner but can't get this working for my use case, I deployed and set as to always run as me but this only seems like half the solution?
My code is below:
//
// Save Data
function submitData() {
var SPREADSHEET_NAME = "Data";
var SEARCH_COL_IDX = 0;
var RETURN_COL_IDX = 0;
var ss = SpreadsheetApp.getActiveSpreadsheet();
var formSS = ss.getSheetByName("Tool"); //Form Sheet
var datasheet = ss.getSheetByName("Data"); //Data Sheet
var str = formSS.getRange("A10").getValue();
var values = ss.getSheetByName(SPREADSHEET_NAME).getDataRange().getValues();
for (var i = 0; i < values.length; i++) {
var row = values[i];
if (row[SEARCH_COL_IDX] != str ) {
//SpreadsheetApp.getUi().alert(' "Dmp #' + formSS.getRange("A4").getValue() + ' "');
// return row[RETURN_COL_IDX];
//} else {
//Input Values
var values1 = [[formSS.getRange("A10").getValue(),
formSS.getRange("B10").getValue(),
formSS.getRange("C10").getValue(),
formSS.getRange("D10").getValue(),
formSS.getRange("E10").getValue(),
formSS.getRange("F10").getValue(),
formSS.getRange("G10").getValue(),
formSS.getRange("H10").getValue(),
formSS.getRange("I10").getValue(),
formSS.getRange("J10").getValue(),
formSS.getRange("K10").getValue()]];
var values2 = [[formSS.getRange("A10").getValue(),
formSS.getRange("B10").getValue(),
formSS.getRange("C10").getValue(),
formSS.getRange("D10").getValue(),
formSS.getRange("E10").getValue(),
formSS.getRange("F10").getValue(),
formSS.getRange("G10").getValue(),
formSS.getRange("I10").getValue(),
formSS.getRange("J10").getValue(),
formSS.getRange("K10").getValue()]];
values2[0].forEach(function(val) {
if (val === "") {
throw new Error("Please fill in Project, Category, Subsystem, Description and Created By Fields.");
}
})
// Save New Data
datasheet.getRange(datasheet.getLastRow()+1, 1, 1, 11).setValues(values1);
SpreadsheetApp.getUi().alert(' New Record Created ');
formSS.getRange("D10").clearContent();
formSS.getRange("E10").clearContent();
formSS.getRange("F10").clearContent();
formSS.getRange("G10").clearContent();
formSS.getRange("H10").clearContent();
formSS.getRange("I10").clearContent();
formSS.getRange("J10").setValue(new Date())
return row[RETURN_COL_IDX];
}
}
}
//=========================================================
// Clear form
function clearCell() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var formSS = ss.getSheetByName("Tool"); //Form Sheet
formSS.getRange("D10").clearContent();
formSS.getRange("E10").clearContent();
formSS.getRange("F10").clearContent();
formSS.getRange("G10").clearContent();
formSS.getRange("I10").clearContent();
formSS.getRange("J10").setValue(new Date())
return true ;
}
//=====================================================================
var SPREADSHEET_NAME = "Data";
var SEARCH_COL_IDX = 0;
var RETURN_COL_IDX = 0;
function searchStr() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var formSS = ss.getSheetByName("Tool"); //Form Sheet
var str = formSS.getRange("F4").getValue();
var values = ss.getSheetByName(SPREADSHEET_NAME).getDataRange().getValues();
for (var i = 0; i < values.length; i++) {
var row = values[i];
if (row[SEARCH_COL_IDX] == str) {
formSS.getRange("A6").setValue(row[0]) ;
formSS.getRange("B6").setValue(row[1]);
formSS.getRange("C6").setValue(row[2]);
formSS.getRange("D6").setValue(row[3]);
formSS.getRange("E6").setValue(row[4]);
formSS.getRange("F6").setValue(row[5]);
formSS.getRange("G6").setValue(row[6]);
formSS.getRange("H6").setValue(row[7]);
formSS.getRange("I6").setValue(row[8]);
formSS.getRange("J6").setValue(row[9]);
return row[RETURN_COL_IDX];
}
}
}
//===================================================================
function rowDelete() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var formSS = ss.getSheetByName("Tool"); //Form Sheet
var datasheet = ss.getSheetByName("Data"); //Data Sheet
var ui = SpreadsheetApp.getUi();
var response = ui.alert(
'Are you sure you want to delete this record?',
ui.ButtonSet.YES_NO);
// Process the user's response.
if (response == ui.Button.YES) {
var str = formSS.getRange("F4").getValue();
var values = ss.getSheetByName(SPREADSHEET_NAME).getDataRange().getValues();
for (var i = 0; i < values.length; i++) {
var row = values[i];
if (row[SEARCH_COL_IDX] == str) {
var INT_R = i+1
datasheet.deleteRow(INT_R) ;
formSS.getRange("A6").clearContent();
formSS.getRange("B6").clearContent();
formSS.getRange("C6").clearContent();
formSS.getRange("D6").clearContent();
formSS.getRange("E6").clearContent();
formSS.getRange("F6").clearContent();
formSS.getRange("G6").clearContent();
formSS.getRange("H6").clearContent();
formSS.getRange("I6").clearContent();
formSS.getRange("J6").clearContent();
return row[RETURN_COL_IDX];
}
}
}
}
//====================================================================
function updateData() {
var SPREADSHEET_NAME = "Data";
var SEARCH_COL_IDX = 0;
var RETURN_COL_IDX = 0;
var ss = SpreadsheetApp.getActiveSpreadsheet();
var formSS = ss.getSheetByName("Tool"); //Form Sheet
var datasheet = ss.getSheetByName("Data"); //Data Sheet
var str = formSS.getRange("A6").getValue();
var values = ss.getSheetByName(SPREADSHEET_NAME).getDataRange().getValues();
for (var i = 0; i < values.length; i++) {
var row = values[i];
if (row[SEARCH_COL_IDX] == str) {
var INT_R = i+1
formSS.getRange("J6").setValue(new Date())
var values1 = [[formSS.getRange("A6").getValue(),
formSS.getRange("B6").getValue(),
formSS.getRange("C6").getValue(),
formSS.getRange("D6").getValue(),
formSS.getRange("E6").getValue(),
formSS.getRange("F6").getValue(),
formSS.getRange("G6").getValue(),
formSS.getRange("H6").getValue(),
formSS.getRange("I6").getValue(),
formSS.getRange("J6").getValue()]];
var values2 = [[formSS.getRange("A6").getValue(),
formSS.getRange("B6").getValue(),
formSS.getRange("C6").getValue(),
formSS.getRange("D6").getValue(),
formSS.getRange("E6").getValue(),
formSS.getRange("F6").getValue(),
formSS.getRange("G6").getValue(),
formSS.getRange("I6").getValue(),
formSS.getRange("J6").getValue()]];
values2[0].forEach(function(val) {
if (val === "") {
throw new Error("Please fill in Revisions, Project, Category, Subsystem, Description and Updated By Fields.");
}
})
datasheet.getRange(INT_R, 1, 1, 10).setValues(values1);
formSS.getRange("A6").clearContent();
formSS.getRange("B6").clearContent();
formSS.getRange("C6").clearContent();
formSS.getRange("D6").clearContent();
formSS.getRange("E6").clearContent();
formSS.getRange("F6").clearContent();
formSS.getRange("G6").clearContent();
formSS.getRange("H6").clearContent();
formSS.getRange("I6").clearContent();
formSS.getRange("J6").clearContent();
formSS.getRange("E4").clearContent();
SpreadsheetApp.getUi().alert(' Record Updated ');
return row[RETURN_COL_IDX];
}
}
}
There are several posts about this, I'll paste a response from one from yesterday. What I recommend specifically in your case is to run the script when there's an edit bye the user in a certain cell. For example a Tickbox, or a Drop-down menu (in a cell) that allows the user to select which function to run:
If you already have an onEdit function working, that's a simple trigger run by whoever is editing the sheet. Meaning that if you protect column A, it won't be editable by that simple trigger because the user won't have permissions
In order to work this out, I encourage you to protect your column as explained here, change your name function or extract in a new function the part about this specific code you're talking about; and set an installable trigger that runs on event. This way it'll be run as you used to but as it came from your own account. As you have permissions for editing ColA the timestamp will be set by the installable trigger but the other user won't be able to edit it since he/she doesn't have the permissions. Try it and let me know!

Google Sheets run the script in all sheets

I am trying to implement the following function on Open sheet but I want to change the tab color on all the sheets. With this function, it allows me to change color only to single tab.
function getFirstEmptyRow() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheetname = "Section A";
var sheet = ss.getSheetByName(sheetname);
var column = sheet.getRange('F:F');
var values = column.getValues(); // get all data in one call
var ct = 0;
while ( values[ct][0] != "" ) {
ct++;
}
Logger.log("Row "+ ct);
var ax=sheet.getRange(ct, 7).getValue();
if(ax == ""){
sheet.setTabColor("ff0000")
} else {
sheet.setTabColor(null)
}
}
Try this:
function onOpen() {
var sheets = SpreadsheetApp.getActiveSpreadsheet().getSheets();
sheets.forEach(s => s.setTabColor("ff0000"));
}
It hard to tell from the provided code, probably you need this:
function onOpen() {
var sheets = SpreadsheetApp.getActiveSpreadsheet().getSheets();
for (var sheet of sheets) {
var data = sheet.getDataRange().getValues(); // get all data
var col_G = data.map(x => x[6]); // get column G
var last_cell = col_G.pop(); // get last cell of column F
if (last_cell == '') sheet.setTabColor("ff0000");
}
}
Or even shorter:
function onOpen() {
var sheets = SpreadsheetApp.getActiveSpreadsheet().getSheets();
for (var sheet of sheets) {
var data = sheet.getDataRange().getValues();
if (data.pop()[6] == '') sheet.setTabColor("ff0000");
}
}
It will make a tab red if in last row that of the table the cell in column G is empty.
If you have several sheets that you want to change you can filter them by names this way:
var names = ['Sheet1', 'Sheet2', 'Sheet3'];
var sheets = SpreadsheetApp.getActiveSpreadsheet().getSheets();
sheets = sheets.filter(s => names.includes(s.getName()));
// the rest of a code
I'm assuming you want to change the tab color for all sheets if the value in column F of the last row of any sheet is blank. There are other values in that row. I've include onOPen, onEdit and onChange.
function onOpen(e) {
// onOpen is a simple trigger and doesn't need to be installed
// don't really use e in this scenario
getFirstEmptyRow();
}
function onEdit(e) {
// onEdit is a simple trigger and doesn't need to be installed
var sheet = e.range.getSheet();
if( e.range.getRow() === sheet.getDataRange().getLastRow() ) {
if( e.range.getColumn() === 6 ) {
if( e.value === "" ) getFirstEmptyRow();
}
}
}
function onChange(e) {
// onChange is an installed trigger
// don't really use e in this scenario
getFirstEmptyRow();
}
function getFirstEmptyRow() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheets = ss.getSheets();
var i=0;
var sheet = null;
var value = null;
var color = null;
for( i=0; i<sheets.length; i++ ) {
sheet = sheets[i];
value = sheet.getRange(sheet.getDataRange().getLastRow(),7).getValue();
color = value === "" ? "ff0000" : null;
sheet.setTabColor(color);
}
}

How to automatically merge cells in Google Sheets upon creation of new Sheet

I am trying to figure out a way to make Google Sheets automatically merge Cells A1-C1 when a new sheet is created. My coworker and I have been trying to figure out the script that would make this happen, but everything we have tried only changes the previous Sheet we were working on, not the new one.
So far these are the two scripts we have tried, just to get some sort of result we are looking for:
function formatCells() {
var ss = SpreadsheetApp.getActiveSpreadsheet ();
var s = ss.getSheetByName('Combined')
var range = s.getDataRange()
var values = range.getValues();
for( var row = values.length -1; row >= 0; --row)
if (values[row][1] == 'Hello')
{s.getRange(row+1,1).mergeAcross();
}
}
and
function newSheetTrigger() {
var ss = SpreadsheetApp.getActive();
ScriptApp.newTrigger('newSheet')
.forSpreadsheet(ss)
.onChange()
.create();
}
function newSheet(e){
if (e.changeType == 'INSERT_GRID') {
SpreadsheetApp.flush();
SpreadsheetApp.getActiveSheet().getRange('A1:C1').merge();
}
}
Does anyone have an idea of where we went wrong?
The problem is that theonChange trigger is not able to detect the active sheet correctly
Retrieving the active sheet on trigger will always return you the first sheet, as you can easily verify with
function myFunction(e) {
Logger.log(e.changeType);
if(e.changeType=="INSERT_GRID"){
Logger.log(SpreadsheetApp.getActive().getActiveSheet().getName());
}
}
So you need to implement a workaround.
For example:
Strore the present sheet names in Script properties
When the trigger fires and the condition e.changeType=="INSERT_GRID" is fullfilled:
Compare the currently present sheet number to the one stored in script properties to evaluate either a new sheet has been inserted
If the sheet number increased - find the name of the new sheet with indexOf()
Merge cells on the new sheet and update the script properties
Code snippet:
var ss = SpreadsheetApp.getActive();
//run me once
function firstSetUp(){
var sheets = ss.getSheets();
var names = [];
for (var i = 0; i < sheets.length; i++){
names.push(sheets[i].getName())
}
PropertiesService.getScriptProperties().setProperty("sheets", JSON.stringify(names) );
}
//run me on trigger
function newSheet(e) {
if(e.changeType=="INSERT_GRID"){
var newSheets = ss.getSheets();
var oldSheetNames = JSON.parse(PropertiesService.getScriptProperties().getProperty("sheets"));
Logger.log(oldSheetNames);
var length = oldSheetNames.length;
Logger.log("length : " + length);
if (length != newSheets.length){
for (var i = 0; i < newSheets.length; i++){
if(oldSheetNames.indexOf(newSheets[i].getName()) == -1){
var newSheet = newSheets[i];
Logger.log(newSheet.getName());
newSheet.getRange('A1:C1').merge();
oldSheetNames.push(newSheet.getName());
PropertiesService.getScriptProperties().setProperty("sheets", JSON.stringify(oldSheetNames));
break;
}
}
}
}
}

Writing code for deleting calendar item created by sheets data

I've written a content management system using Google Sheets that writes assignments to a Google Calendar. The trigger is set up to run manually because any of the Google triggers repopulates the calendar items anytime something changes in the range (I couldn't find a way around it). So, each item is given a unique ID.
This works well, and although you have to run the script again if something changes, the menu is built into the UI. So, it's not to bad. You'll see I also built a UI menu item for deleting the event. I also want to run this manually. There are two sheets in this CMS: One is ongoing projects, which is where this populates. The other is called Completed Projects.
It has all the same data, but when the project is completed, I have a second script that moves it to Completed Projects sheet when they are done (this is based upon a string, and I believe the target is set to onEdit, but it works). Ideally, I would like to have the calendar item be deleted either by running the script using the UI menu (manual trigger, and frankly what I would prefer) or when it moves to the Completed Projects sheet. And this is where I'm running into problems.
I cannot get the logic to work to find the identifier. I was thinking that the way to do this would be to have it look look at the unique ID in the completed folder (it moves with it when the item is completed), and then manually run the deletion from the UI menu. If it finds that ID, then it should remove the calendar item. That script appears in row 7 (column H) in both sheets.
I've tried to make it find the data on that sheet and in that row, and then use the deleteEvent class to call that value and have it delete the event when I trigger the script. The script doesn't generate an error, but it also doesn't delete the calendar item. Here's the code:
function onOpen(e) {
SpreadsheetApp.getUi()
.createMenu('Calendar Actions')
.addItem('Export Assignments', 'exportEvents')
.addSeparator()
.addItem('Delete Assignments', 'deleteEvents')
.addToUi();
};
function exportEvents() {
var sheet = SpreadsheetApp.getActiveSheet();
var headerRows = 1;
var range = sheet.getDataRange();
var data = range.getValues();
var calId = "Calendar ID";
var cal = CalendarApp.getCalendarById(calId);
for (i=0; i<data.length; i++) {
if (i < headerRows) continue;
var row = data[i];
var date = new Date(row[4]);
var title = row[0];
var tstart = new Date(row[4]);
var assignee = row [2]
var tstop = new Date(row[5]);
var id = row[7];
try {
var event = cal.getEventSeriesById(id);
}
catch (e) {
}
if (!event) {
var newEvent = cal.createAllDayEvent(title, tstart, tstop, {description:assignee}).getId();
row[7] = newEvent;
}
else {
event.setTitle(title);
event.setDescription(assignee);
var recurrence = CalendarApp.newRecurrence().addDailyRule().times(1);
event.setRecurrence(recurrence, tstart, tstop);
}
debugger;
}
range.setValues(data);
}
function deleteEvents() {
var sheet = SpreadsheetApp.getActiveSheet();
var headerRows = 1;
var range = sheet.getDataRange();
var data = range.getValues();
var calId = "Calendar ID";
var cal = CalendarApp.getCalendarById(calId);
for (i=0; i<data.length; i++) {
if (i < headerRows) continue;
var row = data[i];
var date = new Date(row[4]);
var title = row[0];
var tstart = new Date(row[4]);
var assignee = row [2]
var tstop = new Date(row[5]);
var id = row[7];
try {
var event = cal.getEventSeriesById(id);
}
catch (e) {
}
if (!event) {
var oldEvent = cal.deleteEvent(title, tstart, tstop, {description:assignee}).getId();
row[7] = oldEvent;
}
}}
I think I am missing something - how can I delete the item?
function deleteEvents() {
var sheet=SpreadsheetApp.getActiveSheet();
var headerRows=1;
var range=sheet.getRange(headerRows + 1,1,sheet.getLastRow(),sheet.getLastColumn());
var data=range.getValues();
var cal=CalendarApp.getCalendarById("Calendar ID");
for (i=0;i<data.length;i++) {
if(data[i][7]) {
cal.getEventById(data[i][7]).deleteEvent();
}
}
}

Google App Script - getCommenter

I'm brand new to Google Apps Script, and I'm trying to create a simple spreadsheet that will allow me to share files by user email through a single spreadsheet.
I have written the following script, which will allow me to add editors and viewers, but not commenters.
I keep getting an error that states that the function addCommenter cannot be found in object spreadsheet.
function shareSheet () {
var ss = SpreadsheetApp.getActiveSpreadsheet();
ss.toast('Updating access level...');
var sheet = ss.getSheets()[0];
var lastRow = sheet.getLastRow();
var range1 = sheet.getRange (3,1, lastRow,9);
var data = range1.getValues();
for (var i= 0; i < data.length; i++) {
var row = data [i];
var accessLevel = row [5];
var values = row [8];
var ss2 = SpreadsheetApp.openById(values);
var values2 = row [4];
// Add new editor
if (accessLevel == 'Edit') {
var addEditors = ss2.addEditor(values2);
}
// Add new viewer
if (accessLevel == 'View'){
var addViewers = ss2.addViewer(values2);
}
// Add new commenter
if (accessLevel == 'Comment') {
var addCommenters = ss2.addCommenter(values2);
}
}
}
The Spreadsheet object does not support the addCommentor() method. You should use DriveApp service instead.
DriveApp.getFileById(id).addCommenter(emailAddress)