Google apps script: loop over tabs to save to separate worksheets - google-apps-script

I would like to loop over all individual tabs of a certain worksheet (preferably skipping the ones of array that need to be excluded). For each individual tab I would like to save it as a separate worksheet in Google drive, with the title of the tab as the file name. If the file name already exists it should be overwritten.
Preferably I would like to be able to exclude an array of sheets (e.g. named 'master' and 'test') in the desired script.
Unfortunately my knowledge of Google Apps scripting is not sufficient enough to write it myself. Any working example is highly appreciated.
EDIT: here is how I solved it (suggestions for improvements are always welcome! :-) )
EDIT2: changed the deleteSheet line
function copySheetsToSS() {
var ss = SpreadsheetApp.getActive();
for(var n in ss.getSheets()){// loop over all tabs in the spreadsheet
var sheet = ss.getSheets()[n];// look at every sheet in spreadsheet
var name = sheet.getName();//get name
if(name != 'master' && name != 'test'){ // exclude some names
var alreadyExist = DriveApp.getFilesByName(name);// check if already there
while(alreadyExist.hasNext()){
alreadyExist.next().setTrashed(true);// delete all files with this name
}
var copy = SpreadsheetApp.create(name);// create the copy
sheet.copyTo(copy).setName(name); // rename copied sheet to same name as SS
copy.deleteSheet(copy.getSheets()[0]);// delete default Sheet1 from new copy
}
}
}

Your description is so accurate that it's quite easy to translate in code...
You should have tried and I'm sure the doc would have allowed you to get it working ... it would have been interesting and so rewarding to run a code you wrote yourself !
Here is the more frustrating (for you) solution of a working code ;-) with explanation in comments
function copySheetsToSS() {
var ss = SpreadsheetApp.getActive();
for(var n in ss.getSheets()){
var sheet = ss.getSheets()[n];// look at every sheet in spreadsheet
var name = sheet.getName();//get name
if(name != 'master' && name != 'test'){ // exclude some names
var alreadyExist = DriveApp.getFilesByName(name);// check if already there
while(alreadyExist.hasNext()){
alreadyExist.next().setTrashed(true);// delete all files with this name
}
var copy = SpreadsheetApp.create(name);// create the copy
sheet.copyTo(copy);// copy content
copy.deleteSheet(copy.getSheets()[0]);// remove original "Sheet1"
copy.getSheets()[0].setName(name);// rename first sheet to same name as SS
}
}
}

Related

Script that adds new row to sheets in selected AND another Google Spreadsheet

I'm working on a Spreadsheet to keep track of team member's project hours. I've created a Spreadsheet per team member for them to fill out weekly, and a project overview Spreadsheet that takes in all data through IMPORTRANGE.
To be able to quickly add a new project I want a macro to insert a new row in the Project overview + the separate Spreadsheets per team member. However I can't figure out how to write the correct code for the separate team member Spreadsheets. What's going wrong here?
If possible I'd also like to make a macro to DELETE a row in the project overview + team member spreadsheets, and one to HIDE a row...
Project overview
Team member Kate
Team member David
My current code:
function InsertRow() {
var ss = SpreadsheetApp.getActive();
var allsheets = ss.getSheets();
var row = SpreadsheetApp.getActiveRange().getRow();
// Array holding the names of the sheets to exclude from the execution
// I only managed to make it work when I exclude the sheet that I actually want to affect instead of the other way around?
var exclude = (["PROJECTS"] ||
SpreadsheetApp.openById("1xjR3lx5_KAA9nqiD3YsjZnulQaMyWGPQqgYsjtzQ0xI").getSheets() ||
SpreadsheetApp.openById("1Q5gtZlqf41of1Zwi8pvZbDx4NN5LcDh5SxfwasLUDMU").getSheets())
for(var s in allsheets){
var sheet = allsheets[s];
// Stop iteration execution if the condition is meet.
if(exclude.indexOf(sheet.getName())==-1) continue;
sheet.insertRowBefore(row);
}
}
As I see it you have a couple of options, which I'll be listing here as A, B, and C. Please note that you might need two different .GS files as you are linking to two sheets
A
Try code found on google app script documentation
I found the google apps script documentation for this command found here, so you might want to check that for this questions and others , but here is the exact code included
// The code below opens a spreadsheet using its ID and logs the name for it.
// Note that the spreadsheet is NOT physically opened on the client side.
// It is opened on the server only (for modification by the script).
var ss = SpreadsheetApp.openById("abc1234567");
Logger.log(ss.getName());
B
use open by url instead of open by id
Your issue might be that your current id isn't correct, I have no way of knowing, so here is some alternate code here (link to documentation here)
// The code below opens a spreadsheet using its id and logs the name for it.
// Note that the spreadsheet is NOT physically opened on the client side.
// It is opened on the server only (for modification by the script).
var ss = SpreadsheetApp.openByUrl(
'https://docs.google.com/spreadsheets/d/abc1234567/edit');
Logger.log(ss.getName());
C
Tie the google script to one sheet
This last option doesn't require any code, just an explanation. Instead of trying to link your script to two separate sheets, you might be able to automatically link it to a single google sheet and create two pages in the sheets file that you treat as two different sheets but are one thing. This might not be what you want, but I included it anyways. You link the sheet to the code automatically by:
1 opening your sheet
2 going to "tools"
3 clicking script editor
4 copy and paste your code (except for the "open by id" part)
5 success!
Your exclude variable doesn't contain what you think it does. You're using an "or" operator (||), which will take the first "truthy" value and skip the rest.
console.log((["PROJECTS"] || 'something else')); // ["PROJECTS"]
Moreover, you don't have a good way of telling which spreadsheet belongs to which team member. To solve that problem, you can create an object.
const teamSpreadsheetIds = {
'DAVID': 'ABC',
'KATE': '123',
};
console.log(teamSpreadsheetIds['DAVID']); // ABC
With the teamSpreadsheetIds object, you can now go about updating your team member sheets locally as well as their individual spreadsheets. The "PROJECTS" sheet is unique, so there's only one check for it.
function InsertRow() {
const ss = SpreadsheetApp.getActive();
const allSheets = ss.getSheets();
const row = SpreadsheetApp.getActiveRange().getRow();
const teamSpreadsheetIds = {
'DAVID': '1Q5gtZlqf41of1Zwi8pvZbDx4NN5LcDh5SxfwasLUDMU',
'KATE': '1xjR3lx5_KAA9nqiD3YsjZnulQaMyWGPQqgYsjtzQ0xI',
};
for (let sheet of allSheets) {
const sheetName = sheet.getName();
const memberSpreadsheetId = teamSpreadsheetIds[sheetName];
const isSkippable = memberSpreadsheetId === undefined && sheetName !== 'PROJECTS';
if (isSkippable) { continue };
// Insert a row in the local sheet
sheet.insertRowBefore(row);
// Get the member sheet and insert a row
if (memberSpreadsheetId) {
const memberSpreadsheet = SpreadsheetApp.openById(memberSpreadsheetId);
const memberSheet = memberSpreadsheet.getSheets()[0]; // Assumes the first sheet is the one to modify
memberSheet.insertRowBefore(row);
}
}
}

Google sheets appscript to copy tabs to new sheets

I have a google sheet with around 190 tabs on that i need to split into 190 different files
The files need to be named the same as the tab, the contents of the tab need to be copied as values but i also need to bring the formatting accross (just not the formulas).
I have looked around, and through a combination of previous questions and answers plus using the function list help have formed the following code. It actually works for the first few tabs but then throws up an error about being unable to delete the only sheet.
function copySheetsToSS() {
var ss = SpreadsheetApp.getActive();
for(var n in ss.getSheets()){
var sheet = ss.getSheets()[n];// look at every sheet in spreadsheet
var name = sheet.getName();//get name
if(name != 'master' && name != 'test'){ // exclude some names
var alreadyExist = DriveApp.getFilesByName(name);// check if already there
while(alreadyExist.hasNext()){
alreadyExist.next().setTrashed(true);// delete all files with this name
}
var copy = SpreadsheetApp.create(name);// create the copy
sheet.copyTo(copy);
copy.deleteSheet(copy.getSheets()[0]);// remove original "Sheet1"
copy.getSheets()[0].setName(name);// rename first sheet to same name as SS
var target_sheet = copy.getSheetByName(name);
var source_range = sheet.getRange("A1:M50");
var target_range = target_sheet.getRange("A1:M50");
var values = source_range.getValues();
target_range.setValues(values);
}
}
}
I am hoping someone can tell me what i have done wrong as I cannot figure it out at this point. I am also open to better solutions though please be aware I am very much a beginner on google appscript, nothing too complex please.
thankyou
In principle your script correctly adds a new sheet to the new spreadsheet before removing the preexisting one
However, mind that calls to service such as SpreadsheetApp are asynchronous.
And this becomes the more noticeable, the longer your script runs.
In your case it apparently leads to behavior that the only sheet is being deleted before the new sheet is being created.
To avoid this, you can force the execution to be synchronous by implementing calls to SpreadsheetApp.flush().
This will ensure that the old sheet won't be deleted before the new one gets inserted.
Sample:
copy.deleteSheet(copy.getSheets()[0]);// remove original "Sheet1"
SpreadsheetApp.flush();
copy.getSheets()[0].setName(name);
You might want to introduce call toflush()` also at other positions where it is important for the code to run synchronously.

Programmatically delete a script

I was wondering if someone could offer me some advice.
I have a master spreadsheet, acting as a template. I have written a script which can be run from the menu (using addToUi command), which makes a copy of template spreadsheet.
The problem is that the script gets copied into the new spreadsheet also which I don't want.
Could anyone suggest a possible way around this please?
I did think a possible way was to get the script to open the copied template and delete the script but not sure if this is possible.
Here is the function which does the copying....
function createCopy() {
var myValue = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet().getRange("B8").getValue();
var destinationFolder = DriveApp.getFolderById("xxxxxxxxxxxxxxxx");
DriveApp.getFileById(SpreadsheetApp.getActiveSpreadsheet().getId()).makeCopy(myValue,destinationFolder);
}
(Cell reference B8 holds the value of what I called the copied spreadsheet).
Rayden, I use a function like that to just copy one sheet to a new spreadsheet and it doesn't drag the script with it. gDrive is the id for the Spreadsheet, tabName the individual sheet you want copied, Filename the name of the copy and destination the destination directory.
//******************************************************************************
//- This function takes a tab and makes it its own file
function tabToSheet(gDrive,tabName,fileName,destination){
var sh = SpreadsheetApp.openById(gDrive);
var ss = sh.getSheetByName(tabName);
//create a new document in the location given
var newSheet = SpreadsheetApp.create("TEMPDELETEME");
//copy the tab from current document to new document
ss.copyTo(newSheet);
var id = newSheet.getId();
newSheet.deleteSheet(newSheet.getSheetByName("Sheet1"));
os = newSheet.getSheets()[0];
os.setName(tabName);
var file = DriveApp.getFileById(id);
var folder = DriveApp.getFolderById(destination);
var finalId = file.makeCopy(fileName, folder).getId();
file.setTrashed(true);
return finalId;
}//*****************************************************************************
The difference is that i'm making a new sheet, then copying the tab rather than copying the entire file. You could add another tab and remove the variables if you want to copy multiple tabs.
I was having trouble implementing J.G.'s approach of moving individual sheets, so the approach I took was to add a conditional in front of the script to only run if the spreadsheet id was equal to the id of the original spreadsheet. My use case was trying to suppress a custom menu on the original workbook from reappearing on the copy, so it worked for that.
var bookId = spreadsheet.getId();
if (bookId === 'original spreadsheet id') {
*Function*
}

Share Google Doc using a Google Script to set an array of emails (in Google Sheets) as editors

I basically am imagining commands that would change the sharing status of sheets it was allowed to edit. For example, I have a master document for a company (a "Roster" for keeping track of where employees are working and which other employees they are working with in that area) and other "Area Sheets" the employees use to keep track of progress. People move around quarterly, so I want to be able to update my Roster and then let the programming update the sharing status of their sheets instead of clicking on every sheet and copy-pasting emails.
As far as I know there is no current possible way to use google products to achieve this. Are there any other ways, barring a New Feature developed by Google?
I don't think I fully understand your requirements, so I'll start be rephrasing what I do understand.
For every employee, generate a list of who they're working with that quarter.
For a certain set of files owned by the employee, make those files editable to the employee's current co-workers.
For point 1, you already have the list built out in Google Sheets, so you can read the contents into arrays for processing. You can use Google Apps Script Spreadsheet Service for this. Example:
var roster = SpreadsheetApp.getActive(); //assuming the script is running within the Roster spreadsheet
var employeesSheet = roster.getSheetByName("Employees"); //assuming sheet name is "Employees"
var employeeEmails = employeesSheet.getRange("B2:B").getValues(); //assuming the emails are in column B with the first row as header
//In this next line I'm going to make a very big simplification.
//I will assume that the coworkers emails are listed as "coworker1#company.com;coworker2#company.com" in the cell.
//This is likely not the case, but you may already have this formatted in a way that works.
//If not, you can merge these values in Google Apps Script, but I'll consider that outside the scope of this question.
var coworkerEmails = employeesSheet.getRange("C2:C").getValues();
For point 2, I assume you already have a way of determining which files need to have their access permissions modified and that you have permission to make those modifications. You'll need to use the Drive Service for this part. In my example, I will assume that all relevant files are saved in a folder specific to each employee and that the folder ID is saved in column D.
var folderIds = employeesSheet.getRange("D2:D").getValues();
for (var i=0; i<employeeEmails.length; i++) {
var folder = DriveApp.getFolderById(folderIds[i][0]);
//At this point, you could give direct access to the entire folder,
//or just to the files within the folder, as I will do.
var files = folder.getFiles();
while (files.hasNext()) {
var file = files.next();
file.addEditors(coworkerEmails[i][0].split(";"));
}
}
Please note that in the above:
This is untested code and meant only to demonstrate how to achieve what I think you're looking for.
No access permissions are being revoked. This would be quite trivial to add, though.
The folder.getFiles() command only gets files that are children of the folder. So, if the folder has child folders, you'll need to traverse through those as well.
You may want to create a custom menu in your Roster spreadsheet to run this script
In the end, including the custom menu, your code could like:
function onOpen() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var menuEntries = [];
menuEntries.push({name: "Reset Permissions", functionName: "resetPermissions"});
ss.addMenu("Roster Actions", menuEntries);
}
function resetPermissions() {
var roster = SpreadsheetApp.getActive();
var employeesSheet = roster.getSheetByName("Employees");
var employeeEmails = employeesSheet.getRange("B2:B").getValues();
var coworkerEmails = employeesSheet.getRange("C2:C").getValues();
var folderIds = employeesSheet.getRange("D2:D").getValues();
for (var i=0; i<employeeEmails.length; i++) {
var folder = DriveApp.getFolderById(folderIds[i][0]);
var files = folder.getFiles();
while (files.hasNext()) {
var file = files.next();
file.addEditors(coworkerEmails[i][0].split(";"));
}
}
}
For reference, this is how I assumed the example "Employees" sheet in the "Roster" spreadsheet to look:

Google Script: Conditionally copy rows from one sheet to another in the same spreadsheet

I read Google Script: Conditionally copy rows from one spreadsheet to another and could not make it work for me.
I need a script that will allow me to do the quoted text below. Please feel free to edit my spreadsheet to make a complete working script. I will keep the spreadsheet publicly available for anyone to copy the script for their own use. I really know nothing about script writing so I need it all spelled out. Sorry, I'm such a noob with all this, but this will help me learn.
https://docs.google.com/spreadsheet/ccc?key=0AoJdwy8V1ldHdFA5M1M1Wlp1NHZhcmxJOUZKVEU4X3c&usp=sharing
If Column B on sheet "All_Mileage" says "John" then I want that row to
be copy and pasted into Sheet "John" starting on row 3 and following.
If Column B on sheet "All_Mileage" says "Adam" then I want that row to
be copy and pasted into Sheet "Adam" starting on row 3 and following.
If Column B on sheet "All_Mileage" says "Mike" then I want that row to
be copy and pasted into Sheet "Mike" starting on row 3 and following.
I saw other scripts on here, but I couldn't get them to work. Like I said I'm greener than a sapling when it come to code.
Thanks a ton!
C.Lang is right, this is not a place to get readymade scripts ... but since this question is so common and has been aswered so often it took me a few minutes to write and test... so there it is :
var ss=SpreadsheetApp.getActiveSpreadsheet();// some global variables
var master = ss.getSheetByName('All_Mileage');
var colWidth = master.getMaxColumns();
function copyRowsOnCondition() {
var data = master.getDataRange().getValues();
for(n=2;n<data.length;++n){
if(data[n][1].length<16){ // if not pre-filled with your text
Logger.log(data[n][1])
var dest = ss.getSheetByName(data[n][1].toString().replace(/ /g,''));//remove any spaces that could be included in the name so the name = sheetName for sure
var destRange = dest.getRange(dest.getLastRow()+1,1);// the place to write
master.getRange(n+1,1,1,colWidth).copyTo(destRange);the copy itself value & format
}
}// loop
}
EDIT : since I used the name value in MasterSheet to find destination sheet I thought it might be usefull to handle the case where the destination sheet doen't exist by creating it using the same rule, ie. name = sheetName...
The other issue was that there was no way to know which rows had been already copied... so I made a version that handles all that, copying only the rows that are manually selected (even in only a single column) and change the background color to tell that these rows have been processed. I also added a menu for a minimal comfort ;-)
(how to keep busy on a cold sunday afternoon ;-)
var ss=SpreadsheetApp.getActiveSpreadsheet();
var master = ss.getSheetByName('All_Mileage');
var colWidth = master.getLastColumn();// last used col in masterSheet
var sheets = ss.getSheets();// number of sheets
function onOpen() {
var menuEntries = [ {name: "Copy selected Rows to sheets", functionName: "copyRowsOnConditionV2"},
];
ss.addMenu("Copy functions",menuEntries);// custom menu
}
function copyRowsOnConditionV2() {
var sheetNames = [];// array of existing sheet names
var sheets = ss.getSheets();// number of sheets
for(s=0;s<sheets.length;++s){sheetNames.push(sheets[s].getName())};
ss.getActiveSelection().setBackground('#ffffbb');
var selectedfirstRow = ss.getActiveSelection().getRowIndex();
var selectedHeigth = ss.getActiveSelection().getHeight()
var selectedFullRange = master.getRange(selectedfirstRow,1,selectedHeigth,colWidth);
var data = selectedFullRange.getValues();
for(n=0;n<data.length;++n){
if(data[n][1].length<16){
if(sheetNames.toString().match(data[n][1].toString().replace(/ /g,''))!=data[n][1].toString().replace(/ /g,'')){// if no sheet exist with this name
var newSheet = ss.insertSheet(data[n][1].toString().replace(/ /g,''),ss.getSheets().length);// then create it
master.getRange(1,1,2,colWidth).copyTo(newSheet.getRange(1,1));// and copy the headers on 2 first rows, then continue as usual
newSheet.getRange(1,1).setValue('Gas Mileage Log - '+data[n][1].toString().replace(/ /g,''));// set name in header
SpreadsheetApp.flush();
var sheets = ss.getSheets();// number of sheets
var sheetNames = [];// update array of existing sheet names
for(s=1;s<sheets.length;++s){sheetNames.push(sheets[s].getName())};
Logger.log(sheetNames)
};
var dest = ss.getSheetByName(data[n][1].toString().replace(/ /g,''));//find the destination sheet
Logger.log(data[n][1].toString().replace(/ /g,''))
var destRange = dest.getRange(dest.getLastRow()+1,1);// define range
master.getRange(selectedfirstRow+n,1,1,colWidth).copyTo(destRange);// and make copy below last row
}
}
}
Illustration below :