I have a Google Add-on that manages a few sheets in a Google spreadsheet. One of the things it does is remove sheets based on dates, which are the names of the sheets it deletes. In my current development phase I'm also adding the capability to remove sheets that include the date and another term, specifically "script 6/5/2019", for example.
I'm using the same code that worked for the sheets named with the date and made some adjustments to it, but it returns an error when it comes time to delete the sheet: "Cannot find method deleteSheet(string)"
if(sRemove==true) {
SpreadsheetApp.getActiveSpreadsheet().toast('Finding Old Scripts', 'Status',3);
for (var i = ss.getNumSheets()-1; i >= 0; i--) {
var thisTab = ss.getSheets()[i].getName();
if(thisTab.substr(0,6) =="script") {
Logger.log(i+" "+thisTab+" is a script");
var tabDate = new Date(thisTab.substr(8));
//8 is the first digit of the date
Logger.log(tabDate);
var thisDate = new Date(todayDate);
var patt = new RegExp("s");
var res = patt.test(thisTab);
Logger.log(i+" "+res);
if(tabDate<thisDate && res==true) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
//ss.setActiveSheet(ss.getSheetByName(thisTab));
//ss.deleteActiveSheet(ss.getSheetByName(thisTab));
Logger.log(thisTab);
ss.deleteSheet(thisTab);
tabsGone++;
}
}
}
ui.alert(tabsGone+" Sheets Removed");
}
Logger.log returns the correct name of the sheet to be removed, but deleting the sheet returns the error "Cannot find method deleteSheet(string)"
Requirement:
Delete sheet based on name.
Solution:
Use getSheetByName() to get the sheet object then pass this to your deleteSheet():
ss.deleteSheet(ss.getSheetByName(thisTab));
Explanation:
Currently you're passing string thisTab to deleteSheet(), this won't work because it is expecting a sheet object, not a string. As long as your code above is working properly and thisTab matches your sheet name exactly, all you need to do is call getSheetByName(thisTab) to get the sheet object then pass this to deleteSheet().
References:
getSheetByName(string)
deleteSheet(sheet)
Related
OK, so my main question here is if I can reduce the time it takes for this script below to run--it takes on average 8 minutes, 5-6 of which is just from copying sheets into another spreadsheet and pasting their original display values over them.
The purpose of this is to have vendor sheets from four different spreadsheets totaled in alphabetical order for sending as-is or for printing. I already researched trying do use PDFs instead, but have not been able to find a way to be able to print in a specific order with the resources I have--which do not include Adobe premium or access to printers for something like powershell scripts, which I do not know anyway. (Printing a folder of files does not actually print in sequential order, unfortunately, so generating PDFs of each sheet does not give me an option to print in alphabetical order.)
So, my workaround is to get an array of the four source spreadsheets, loop through those with another loop that goes through each sheet, and for each sheet make a copy to the merge spreadsheet, and then get and paste over each sheet with the display values as there would otherwise be issues with formulas not referencing. Then it calls other functions to rename the sheets without "Copy of " on them and then to place them in alphabetical order. For ongoing use, it starts by deleting all the old sheets except for an Index sheet from the merged spreadsheet.
Is there any way to reduce the number of calls? I looked into while loops, but it's different in that a lot of the "data" I'm moving is actual entire sheets, and I need their formatting as well. I considered just inserting the same number of sheets as I would be copying, and then repeating copying the same format which they all have, but that hits into an issue with the names--there can sometimes be duplicate named sheets between the source spreadsheets which have to be allowed in, and copying in just makes additional ones have a number at the end. That is fine, but I cannot set the names of the sheets before they are in the merged spreadsheet due to this, unless I can somehow check for duplicate sheet names and add "1", "2", etc, to them to allow them in.
Is the time running this just inevitable for this process, or can I speed it up?
//https://stackoverflow.com/questions/58834873/copy-google-sheet-values-to-another-google-spreadsheet-and-create-new-sheet-with?rq=1
//https://stackoverflow.com/questions/36758479/google-apps-script-to-iterate-through-a-list-of-spreadsheet-ids-and-copy-range
//Global variables. Runs from the merging spreadsheet, and the IDs of the four source Vendor spreadsheets
var mergeSpreadsheet = SpreadsheetApp.getActive();
var indexSheet = "Index"; //global for alphabetzing script
var monthly732 = '1TLH8HencpyH-4pDkQ1LetTErRxqYjh4gYC0XDdQxSVM'
var monthly827 = '19UfhUAvpFi0UJBF-rQoc4LWcL20-79nRltbSL-Wpj0A'
var quarterly732 = '1BRhoO_GcEoBmV_SoaV2xkw9BjgasAX3CorKxgWuEd2I'
var quarterly827 = '1JaAQtRIiCaQjO_A5S0p-VjHk8LUXExmqOYo75LxWf58'
//main function
function mergeTest2(){
deleteAllSheets(); //calls function at end of script to delete old sheets from merged spreadsheet
//Array of source spreadsheets for looping through
var idList = [monthly732, monthly827, quarterly732, quarterly827];
for (var i = 0; i < idList.length; i++){
//get sheets from each source spreadsheet
var allsheets = SpreadsheetApp.openById(idList[i]).getSheets();
//For each spreadsheet, iterate through sheets except for Index and Template
// Stop iteration execution if the condition is meet.
for(var s in allsheets){
var sheet = allsheets[s];
if(
(sheet.getName() == "Index") ||
(sheet.getName() == "Template") ||
(sheet.getName() == "Totals")
) continue;
//For each sheet, copyTo merged spreadsheet, get original's display values and paste them on the copy
//Using copyTo to get format of sheet, and allow for duplicate sheet names added
var mergeSheet = sheet.copyTo(mergeSpreadsheet);
//Getting display values to override formulas to avoid REF errors
var sValues = sheet.getDataRange().getDisplayValues();
mergeSheet.getRange(1,1,sValues.length,sValues[0].length).setValues(sValues);
} //allsheets, end of for-loop for sheets within a spreadsheet
} //end for-loop of array of spreadsheets
//Get numbers of sheets from each spreadsheet for comparing to make sure all applicable ones were copied
//formulas set in Index page for comparing applicable sheets from sources (Vendors only) to make sure they equal merged Vendor sheets at the end
var index_sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(indexSheet);
index_sheet.getRange("C2").setValue(SpreadsheetApp.openById(monthly732).getNumSheets());
index_sheet.getRange("D2").setValue(SpreadsheetApp.openById(monthly827).getNumSheets());
index_sheet.getRange("E2").setValue(SpreadsheetApp.openById(quarterly732).getNumSheets());
index_sheet.getRange("F2").setValue(SpreadsheetApp.openById(quarterly827).getNumSheets());
index_sheet.getRange("B2").setValue(mergeSpreadsheet.getNumSheets());
//Loggers used for testing, replace with getNumSheets above in final
Logger.log(SpreadsheetApp.openById(monthly732).getNumSheets());
Logger.log(SpreadsheetApp.openById(monthly827).getNumSheets());
Logger.log(SpreadsheetApp.openById(quarterly732).getNumSheets());
Logger.log(SpreadsheetApp.openById(quarterly827).getNumSheets());
Logger.log(mergeSpreadsheet.getNumSheets());
slicerOfNames(); //call slicer function below for removing "Copy of " from sheet names.
sortSheetsByName(); //call function to put sheets in alphabetical order
} //end mergeTest2 main function
//Function to get sheet names except for Index, or that all have "Copy of " in name, and slice(8)
function slicerOfNames(){
mergeSpreadsheet.getSheets().forEach(function(sheet) {
var sheetName = sheet.getSheetName();
if (sheetName.indexOf("Copy of ") == -1) {
Logger.log(sheetName);
} else {
sheet.setName(sheetName.slice(8));
} //end if/else
}) //end function(sheet)
} //end slicer function
//Function for alphabetical ordering of sheets within merged spreadsheet
function sortSheetsByName() {
var aSheets = new Array();
var ss = SpreadsheetApp.getActiveSpreadsheet();
var allsheets = ss.getSheets();
for (var s in allsheets)
{
var ss = SpreadsheetApp.getActive();
var sheet = allsheets[s];
if(
(sheet.getName() == indexSheet)
) continue;
aSheets.push(ss.getSheets()[s].getName());
}
if(aSheets.length)
{
ss.getSheetByName(indexSheet).activate()
ss.moveActiveSheet(1)
aSheets.sort();
for (var i = 0; i < aSheets.length; i++)
{
var theSheet = ss.getSheetByName(aSheets[i]);
if(theSheet.getIndex() != i + 2){
ss.setActiveSheet(theSheet);
ss.moveActiveSheet(i + 2);
} //end if statement
} //end for-loop
} // end if(aSheets.length)
}//end alphabetization function
//Function to delete old sheets from merged spreadsheet at the beginning of the script
function deleteAllSheets() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var result = SpreadsheetApp.getUi().alert("This will delete all sheets except the Index, are you sure?", SpreadsheetApp.getUi().ButtonSet.OK_CANCEL);
if(result === SpreadsheetApp.getUi().Button.OK) {
var sheets = ss.getSheets();
for (i = 0; i < sheets.length; i++) {
switch(sheets[i].getSheetName()) {
case "Index":
break;
default:
ss.deleteSheet(sheets[i]);}}
} else {
SpreadsheetApp.getActive().toast("Sheets not deleted");
}
} //end delete sheets function
In your code, it looks you set values of disjoint ranges five separate times, which is very expensive. You could combine these into one line like combined_range.setValues(2D_array). If you are worried about overwriting any existing data, use combined_range.getValues(), and then manipulate the returned 2D array using Apps Script prior to setting.
Following a similar philosophy, logging can be made much more efficient as well; in general, consider implementing a hash map to store and organize the results of frequent spreadsheet calls. Alternatively, variables can also otherwise be used to condense, hasten, and improve overall readability.
Example Document: Link
I have had a working script that would add or remove editors from a specified sheet ID for a good few months until recently it has started giving an error of:
Exception: The parameters (number[]) don't match the method signature for SpreadsheetApp.Spreadsheet.removeEditor.
Nothing has changed recently regarding the input I am providing the script so I am at a bit of a loss.
The script is as follows:
function runEmailAccess(){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sEditors = ss.getSheetByName('Editors');
var sheet = SpreadsheetApp.openById("SHEETID");
var nAddEditor = sEditors.getRange('A2').getValue();
if (nAddEditor != 0){
var vAddEditor = sEditors.getRange('A3:A'+nAddEditor).getValues();
sheet.addEditors(vAddEditor);
}
var nRemoveEditor = sEditors.getRange('B2').getValue();
if (nRemoveEditor != 0){
var vRemoveEditor = sEditors.getRange('B3:B'+nRemoveEditor).getValues();
for (j=0;j<vRemoveEditor.length;j++) {
sheet.removeEditor(vRemoveEditor[j])
}
}
}
The script takes the row number of last email in the list from Row 2 then takes the emails for row 3 to that row via .getRange.
Any help regarding this would be of great help.
Thanks.
vRemoveEditor is 2D array. You're indexing only into the outer array with vRemoveEditor[j]. You need to index into both to get primitive values: vRemoveEditor[j][0]
I have made a google script function to update a specific page for multiple different spreadsheets with identical pages. So the function takes the name and position of the sheet to be changed (from a master spreadsheet) and then loops through each of the other spreadsheets and replaces the old specified sheet with the new specified one. My problem is some of the sheets need to be protected but when it replaces them it removes protection.
I've tried to use sheet.protect() but I get "TypeError: Cannot find function protect in object Spreadsheet". I am unaware why since in the google docs I use similar code. I also tried using the getProtections() but it couldn't find the function either.
function updatePage(pageName, pagePos) {
var source = SpreadsheetApp.getActiveSpreadsheet();
var sheetToUp = source.getSheetByName(pageName);
for (i=0; i < schools.length; i++) {
//schools[i][1] provides the link to the spreadsheet
if(schools[i][1] != 0) {
sheet = SpreadsheetApp.openById(schools[i][1]);
sheetToUp.copyTo(sheet);
sheet.deleteSheet(sheet.getSheetByName(pageName));
sheet.setActiveSheet(sheet.getSheetByName("Copy of " + pageName));
sheet.renameActiveSheet(pageName);
sheet.setActiveSheet(sheet.getSheetByName(pageName));
sheet.moveActiveSheet(pagePos);
var protection = sheet.protect(); //problem
}
}
}
It should output a new protected sheet with name pageName at position pagePos in the spreadsheet linked in school[i][1]. It outputs this but its unprotected.
I am using google sheets to import data into a mysql database. Each month a new sheet gets created for instance 06_2017. Is there a way to set the newly created sheet as active sheet or based on current month & year set that sheet as active. Instead of having to hard code
var gsheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('06_2017');
var gsheetname = sheet.getSheetName();
var gdata = sheet.getDataRange().getValues();
As #Cooper have said, you can store the name of the newly created sheet in UserProperties, then place your code in onOpen() method of your script.
You can do it like this,
function onOpen(e)
{
var prop = PropertiesService.getDocumentProperties();
var sheet_name = prop.getProperty(<NAME_OF_SHEET>);
var sp = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(sheet_name).activate();
}
If you are going to stick to the pattern 'MM_YYYY' for naming your sheets, the following code will always activate the sheet with the latest date. If your plan is to use different naming patterns in a single document, you need to refactor the code below to allow for this.
The onOpen() function is one of the so-called simple triggers in GAS, so every code inside this function will be executed when you open the document. The sort() method will sort array elements based on given criteria. Since sort() doesn't know what criteria you'd like to use for sorting Sheet objects, you need to pass a comparator function to override the default one.
The comparator function parses sheet names and creates date objects by passing year and date (in that order) to the new Date() constructor. It then uses the result of date subtraction to sort the 'sheets' array in descending order, from the highest to the lowest value.
Finally, you take the first element of the sorted array (that would be the latest created sheet) and set it as the active sheet.
function onOpen() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheets = ss.getSheets();
sheets.sort(function(a,b) {
var arr1 = a.getName().split("_");
var arr2 = b.getName().split("_");
return new Date(arr2[1], arr2[0]) - new Date(arr1[1], arr1[0]);
});
if (sheets.length < 1) {
return;
}
ss.setActiveSheet(sheets[0]);
}
Situation:
I have a spreadsheet with 20 sheet.
I have other script that copy sheets from other spreadsheet every days to this spreadsheet-
I need to delete every days some specific sheet from a particular spreadsheet.
Problem:
When the script ends to clear the sheets, the spreadsheet hangs and I have to exit and re-enter to the spreadsheet.
I'll appreciate if anyone can help to tunning this script to work without hangging the spreadsheet.
Script:
function shellDeleteSheets(){
var sheets = ['Sheet1','Sheet2','Sheet3','Sheet4','Sheet5'];
for (var s in sheets){
deleteSheetName(sheets[s]);
}
}
function deleteSheetName(stname) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sh = ss.getSheetByName(stname);
if(!sh) {
return;
}
ss.setActiveSheet(sh);
ss.deleteActiveSheet();
Utilities.sleep(400);
SpreadsheetApp.flush();
}
Try this version I use without issue
function DeleteSheets(){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheets = ['sheet1','sheet2','sheet3'];
var numberOfSheets = ss.getSheets().length;
for(var s = numberOfSheets-1; s>0 ; s--){ // in my case I never delete the first sheet
SpreadsheetApp.setActiveSheet(ss.getSheets()[s]);
var shName = SpreadsheetApp.getActiveSheet().getName();
if(sheets.indexOf(shName)>-1){
var delSheet = ss.deleteActiveSheet();
Utilities.sleep(500);
}
}
SpreadsheetApp.setActiveSheet(ss.getSheets()[0]);// send me back to first sheet
}
You could of course use the array of names as an argument for the function or - that's what I do in some cases - give the names of the sheet I need to keep, in this case the if condition is just different .
the sleep can be 400 mS, not sure it makes any difference, I use 500 because at some time I found it more reliable... and I (try to) never change a working solution ;-)
EDIT following your comment :
to make active the sheet called 'Updated' just change the last line of the code like this :
SpreadsheetApp.setActiveSheet(ss.getSheetByName('Updated'));
please note also that I moved this line in the original code, outside of the loop, sorry for this small error ^^