Every week I have a few different Google Sheets that I have to download as excel, delete hidden rows, fix data and then email out.
This can be very time consuming, so I would like to make/deploy a script that automates this process.
I did a ton of searching to try to get this to work, however I am running into a few issues.
First, the data in the sheet is pulled via a query/importrange formula, so once downloaded to excel all data looks like the following:
=IFERROR(__xludf.DUMMYFUNCTION("""COMPUTED_VALUE"""),"No")
This prevents being able to filter any of the data. Currently I'm fixing this by copying all data in excel and pasting values only. I'd like the excel file to be corrected so that I don't have to do that.
Also, I would like to be able to email the converted Excel file.
I found the following code that works for this:
function getGoogleSpreadsheetAsExcel(){
try {
var ss = SpreadsheetApp.getActive();
var url = "https://docs.google.com/feeds/download/spreadsheets/Export?key=" + ss.getId() + "&exportFormat=xlsx";
var params = {
method : "get",
headers : {"Authorization": "Bearer " + ScriptApp.getOAuthToken()},
muteHttpExceptions: true
};
var blob = UrlFetchApp.fetch(url, params).getBlob();
blob.setName(ss.getName() + ".xlsx");
MailApp.sendEmail("amit#labnol.org", "Google Sheet to Excel", "The XLSX file is attached", {attachments: [blob]});
} catch (f) {
Logger.log(f.toString());
}
}
This mostly works, but again runs into the issue above of the importrange data being incorrect and showing as the =IFERROR(__xludf.DUMMYFUNCTION.
There are also two columns (A and J) that are hidden and need to be deleted.
Additionally, I would love to be able to change the email address, subject and message via an GUI. But this is much less important and not needed.
I know that this is a big ask, but any help would be amazing!
This script addresses part of your question.
Create a copy of a sheet, and in the process convert formulas to values
Delete columns
Note: There are several options for converting the formula to values.
So far as changing the email address, subject and message via a GUI, I suggest using a custom sidebar.
function so5665379502(){
/*
// The purpose of this script is to copy the content of one sheet to another;
// converting formulas to values
// and deleting columns
*/
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheetname = "ejbsheet01";
var sourcesheet = ss.getSheetByName(sheetname);
var destname = "ejbsheet02";
var destinationSheet = ss.getSheetByName(destname);
sourcesheet.activate();
var range = sourcesheet.getDataRange();
var rFC = range.getColumn();
var rLC = range.getLastColumn();
var rFR = range.getRow();
var rLR = range.getLastRow();
//Logger.log("DEBUG: the data range = "+range.getA1Notation()+", column 1 = "+rFC+", the last column = "+rLC+", first row = "+rFR+", last row = "+rLR);
// syntax copyValuesToRange(sheet, column, columnEnd, row, rowEnd)
range.copyValuesToRange(destinationSheet, rFC, rLC, rFR, rLR);
// delete column J first so that other column numbers don't chnage.
// column J = 10
destinationSheet.deleteColumn(10);
// Delete Column A
// column A = 1
destinationSheet.deleteColumn(1);
}
BEFORE
Note: Columns A and J are hidden
AFTER
Note: The hidden Columns are gone and the data ais values, not formula.
Related
I have a Google Form that saves data into a Google Sheet. The form is completed by students in an online class.
Additional data is periodically added to each row of the Google Sheets by their teacher to track their attendance. I'd like to be able to share a read-only mirror of the data with each student such that they can only see their own data. (All data from a given student is in a single row of the worksheet).
I know that could do this manually by creating a new sheet for each student, using =IMPORTRANGE() to mirror their row of data into the sheet, and share a link to the sheet with the student. However, this isn't feasible given the number of sheets that would need to be manually created.
Is there a way to automate sharing of a limited section of the data with people?
This script will create a spreadsheet per row and will have its first row equate to the first row of the original spreadsheet (header) and use IMPORTRANGE on its 2nd row. It will then add the email in that row as viewer to the newly created spreadsheet.
Script:
function exportData() {
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var sheet = spreadsheet.getActiveSheet();
var range = sheet.getDataRange();
var values = range.getValues();
var header = values.shift();
var ssId = spreadsheet.getId();
values.forEach((row, index) => {
var newName = `Data: ${row[2]}, ${row[1]}`;
var files = DriveApp.getFilesByName(newName);
// file is already existing
if (files.hasNext())
return;
var newSpreadsheet = SpreadsheetApp.create(newName);
var newSheet = newSpreadsheet.getActiveSheet();
var rowNum = index + 2;
var newSsId = newSpreadsheet.getId();
// append header to new file's first row
newSheet.appendRow(header)
// use importrange on A2
newSheet.getRange(2, 1).setFormula(`=IMPORTRANGE("${spreadsheet.getUrl()}", "${sheet.getSheetName()}!A${rowNum}:${rowNum}")`);
// add permission to automatically allow access to importrange
addImportrangePermission(ssId, newSpreadsheet.getId());
// add the email in the third column as the viewer
DriveApp.getFileById(newSsId).addViewer(row[5]);
});
}
function addImportrangePermission(donorId, ssId) {
// adding permission by fetching this url
const url = `https://docs.google.com/spreadsheets/d/${ssId}/externaldata/addimportrangepermissions?donorDocId=${donorId}`;
const token = ScriptApp.getOAuthToken();
const params = {
method: 'post',
headers: {
Authorization: 'Bearer ' + token,
},
muteHttpExceptions: true
};
UrlFetchApp.fetch(url, params);
}
Original spreadsheet:
Sample Output:
Sample new spreadsheet content:
Note:
Script is updated based on your sample data
addViewer will fail if email is invalid so be sure to use one. Be sure to adjust the column passed to row to avoid failing.
I'm trying to write a script that will allow me to email a list of 150 employees with their individual sales data for the week.
At the moment I have a main front sheet with a Column for Email, Subject, and Store number. Each store number correlates to a Sheet (tab) with the same name, for example joe#gmail.com at store number 5070 has a tab named '5070' with changing data.
The problem I'm having is referencing the changing variable sheet name.
function sendEmail() {
var ss = SpreadsheetApp.getActiveSpreadsheet()
var sheet1=ss.getSheetByName('Sheet1');
var n=sheet1.getLastRow();
for (var i = 2; i < n+1 ; i++ ) {
var emailAddress = sheet1.getRange(i,1).getValue();
var subject = sheet1.getRange(i,2).getValue();
var message = sheet1.getRange(i,3).getValue();
MailApp.sendEmail(emailAddress, subject, message);
}
}
I am very new to the whole thing and have been searching around but have not had much luck. Thank you in advance!
You can't send a sheet. You can send only a link to the sheet.
If you replace this:
var message = sheet1.getRange(i,3).getValue();
with this:
var sheet_name = sheet1.getRange(i,3).getValue();
var sheet = ss.getSheetByName(sheet_name);
var message = sheet.getUrl();
Your recipients will get the link to the spreadsheet (a whole sheet, not even to the particular sheet).
To send a link to a particular sheet of the spreadsheet you need a bit more complicated solution:
var sheet_name = sheet1.getRange(i,3).getValue();
var sheet = ss.getSheetByName(sheet_name);
var message = getSheetUrl(sheet);
function getSheetUrl(sht) {
// credits: https://webapps.stackexchange.com/questions/93305/
var ss = SpreadsheetApp.getActive();
var url = '';
sht = sht ? sht : ss.getActiveSheet();
url = (ss.getUrl() + '#gid=' + ss.getSheetId());
return url;
}
But all your recipients will see all the spreadsheet anyway with all its sheets. In case this is not the thing you want you have three options:
Option 1 -- Make a new spreadsheet, copy the data into it and send the link to this new spreadsheet.
Option 2 -- Make PDF from the sheet and send it. Actually you will need to perform Option 1 first, convert the new spreadsheet to PDF, and delete the new spreadsheet after you send it (as PDF).
Option 3 -- make a HTML table (or text table, why not?) from the data of the sheet and send the table.
I'm trying to write some Apps Script to pull only the email addresses which match from an array of companies into another sheet for processing. This should also remove the #company.com extension and leave me with just the username.
So on Sheet 1:
In Column E, we have the Company names under Header: "Company"
In Column D, we have the Email Addresses under Header: "Emails"
On Sheet 2:
In Column A, we need the Username result under Header: "Username"
Currently the below script which should have 2 results, comes back with:
this
Any help with this would be immensely appreciated as I'm losing my mind.
Thank you!
function pullUsernames() {
//Get Sheet1
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("Sheet1");
//Set Values for Search
var valuesToWatch = ['Company1','Company2'];
var range = sheet.getRange(2,5,sheet.getLastRow()-1).getValues();
var emails = sheet.getRange(2,4,lr-1).getValues();
//For Loop to match values, etc.
for(var i=0; i < range.length; i++){
if(valuesToWatch.indexOf(range[i][0])){;
var targetSS = SpreadsheetApp.openById("1hpIIgkXMgrlOfYqfS4A3ro8BFQB02dAy5G40Y7vUI2c").getSheetByName("Sheet2");
var targetRange = targetSS.getRange(i+1,1,targetSS.getLastRow(),1);
var usernames = String(emails[i][0]).replace("#company.com", "")
targetRange.setValue(usernames[i][0]);
} //if
} //for
} //Function
There were a bunch of issues in your code and I decided to optimize them. Feel free to modify the code if needed.
Issues:
Both range and emails can be fetched together instead of separate
Brought targetSS outside the loop since it is only static based on your issue
I don't quite get your getRange, I assume you want to append the usernames on the last row? If so, then use appendRow instead.
Since I didn't use the array index now, I used includes instead to check if the data is in valuesToWatch.
Your email string manipulation is quite hard coded so I replaced it with a better solution, removing anything but the characters before #.
Code:
function pullUsernames() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("Sheet1");
// update your targetSS, made it under same ss for easier testing
var targetSS = ss.getSheetByName("Sheet2");
// set values for search
var valuesToWatch = ['Company1', 'Company2'];
// get email and company columns in one go
var data = sheet.getRange(2, 4, sheet.getLastRow() - 1, 2).getValues();
// each row of data contains email and company formatted as [email, company].
data.forEach(function ([email, company]){
if(valuesToWatch.includes(company))
// remove anything from # onwards from email and append at last row
targetSS.appendRow([email.substring(0, email.lastIndexOf("#"))]);
});
}
Sample Data:
Sample Output:
I am trying to create export to PDF to my drive. This script currently does it. However I only want a specific range of cells to export out and I would like the name of the saving file to pull the information from a Cell in the sheet. Please help! I have the following script:
function getpdf2(){
SpreadsheetApp.flush();
var theurl = 'https://docs.google.com/spreadsheets/d/'
+ '1C-ONDP4pZeUrlMYekvg0i8oNceASL1s4_o8oBpQuzHk'
+ '/export?exportFormat=pdf&format=pdf'
+ '&size=LETTER'
+ '&portrait=false'
+ '&fitw=false'
+ '&top_margin=0.50'
+ '&bottom_margin=0.50'
+ '&left_margin=0.50'
+ '&right_margin=0.50'
+ '&sheetnames=false&printtitle=false'
+ '&pagenum=false'
+ '&gridlines=false'
+ '&fzr=FALSE'
+ '&gid='
+ '1101884643';
var token = ScriptApp.getOAuthToken();
var docurl = UrlFetchApp.fetch(theurl, { headers: { 'Authorization': 'Bearer ' + token } });
var fileid = DriveApp.createFile(docurl.getBlob()).setName('CAITLYN!B7.pdf').getId();
var pdf = docurl.getBlob().setName('CAITLYN!B7.pdf');
var pdf = docurl.getBlob().getAs('application/pdf').setName('testss.pdf');
var filetodel = DriveApp.getFileById(fileid);
if (DriveApp.getFoldersByName("PDF Quotes").hasNext()){
var folder = DriveApp.getFoldersByName("PDF Quotes").next();
filetodel.makeCopy(folder);
}
DriveApp.removeFile(filetodel);
}
Since there is no native way to export specific range, we need to access the data inside the spreadsheet.
What we need to do to simplify the exporting process is we copy the data range into a temporary sheet, and then paste it to the first set of cells.
After that, we need to hide the cells outside the copied cells.
Lastly, we export that temporary sheet into a pdf in our drive.
Here is the whole working code.
function exportRangeToPdf() {
// Get the sheet id, sheet name, range of cells you want to export
// and the folder ID in your drive where you want to store the pdf
var sheetId = '1ZzkBAR1EgismFCd-hxOUyC_uZKYucu7Av-lDHhrEMx4';
var sheetName = 'Sheet1';
var sheetSrcRange = 'B5:C6';
var driveId = '0B55C21aJsSBlfk9FTjRqOG8tb3hjR1N4MTU1YjVPNU4weGVhSldfU3F4OXladVVNMF9Ccms';
var sheet = SpreadsheetApp.openById(sheetId).getSheetByName(sheetName);
var range = sheet.getRange(sheetSrcRange);
var sheetDstRange = 'A1:' + sheet.getRange(range.getHeight(), range.getWidth()).getA1Notation();
var rangeValues = range.getValues();
var folder = DriveApp.getFolderById(driveId);
// Feel free to modify if needed
// From sheet name (e.g. Sheet1.pdf)
var pdfName = sheetName + '.pdf';
// From specific cell, can be inside or outside your selected range to export (e.g. exportedPDF.pdf)
// var pdfName = sheet.getRange('C7').getValue() + '.pdf';
// From the combination of sheet name and range you chose (e.g. Sheet1_B5:C6.pdf)
// var pdfName = sheetName + '_' + sheetSrcRange + '.pdf';
// Create a blank spreadsheet to be able to export just the range
var destSpreadsheet = SpreadsheetApp.create('PDF');
// If our range is 2 rows and 2 columns we want it to be copied from A1 to B2
// Then we can hide the rest of columns and rows and export
var sheet2 = destSpreadsheet.getSheetByName('temp');
if(!sheet2){
destSpreadsheet.insertSheet('temp').getRange(sheetDstRange).setValues(rangeValues);
var sheet2 = destSpreadsheet.getSheetByName('temp');
}
// Hide all the rows and columns that do not have content
sheet2.hideRows(sheet2.getLastRow() + 1, sheet2.getMaxRows() - sheet2.getLastRow());
sheet2.hideColumns(sheet2.getLastColumn() + 1, sheet2.getMaxColumns() - sheet2.getLastColumn());
// Delete the first sheet that is automatically created when you create a new spreadsheet
destSpreadsheet.deleteSheet(destSpreadsheet.getSheetByName(sheetName));
// Export our new spreadsheet to PDF
var theBlob = destSpreadsheet.getBlob().getAs('application/pdf').setName(pdfName);
folder.createFile(theBlob);
// Delete the spreadsheet we created to export this range.
DriveApp.getFileById(destSpreadsheet.getId()).setTrashed(true);
}
Sample Data:
Output:
I have solved the same challenge in a different way that eliminates code and works effectively for me. Instead of writing a script to create a tab, copy data, delete what I don't want, etc. I just create an "Export" tab and use query to pull in the data I want to export on a regular basis.
In my case, I want to export columns in a different order, so I use arrayformula to pull in individual columns from the Primary Data Tab into an array and then run a query against it. Notice I also filter data ("Col7<>'Completed') in the query to pull in only the rows I want. Finally, you'll see that the filter references Col7 and not a column letter. When you use arrayformula, you must refer to the columns by their position in the array (Col1, Col2, etc.)
=query(ArrayFormula({'Primary Data Tab'!AJ:AJ,{'Primary Data Tab'!G:G}&" "&{'Primary Data Tab'!I:I},'Primary Data Tab'!M:M,'Primary Data Tab'!E:E,'Primary Data Tab'!F:F,'Primary Data Tab'!AK:AK,'Primary Data Tab'!AI:AI,'Primary Data Tab'!W:W,'Primary Data Tab'!K:K,'Primary Data Tab'!B:B,'Primary Data Tab'!C:C,'Primary Data Tab'!Y:Y}),"select * where Col7<>'Completed' and Col7<>'Cancelled'",1)
You can simplify this by just bringing in the entire sheet and using a query to select columns and filter it:
=query('Primary Data Tab'!A:AJ,"select A,B,D,G,T,AD where G<>'Completed' and G<>'Cancelled'",1)
Once you've done this, the data in the tab automatically reflects the Primary Data Tab and you can just export it at any time.
In Google Spreadsheet I would like to take only the values from a complete list on one spreadsheet and append it to the bottom of a list on another spreadsheet. My trouble is that using the the copyValuesToRange() function errors the following:
Target sheet and source range must be on the same spreadsheet.
Here's my current code:
function transferList() {
var source = SpreadsheetApp.getActiveSpreadsheet();
var target = SpreadsheetApp.openById("0ABCD");
var target_sheet = target.getSheetByName("RFPData");
var sheet = source.getSheetByName("RFP List");
var sheet_last_row = sheet.getLastRow() + 1;
var source_range = sheet.getRange("A2:I"+sheet_last_row);
var sWidth=source_range.getWidth() + 1;
var sHeight=source_range.getHeight() + 1;
var last_row=target_sheet.getLastRow();
source_range.copyValuesToRange(target_sheet , 1, sWidth,
last_row + 1, last_row + sHeight );
}
Any idea how I can get this to work?
As you've found, copyValuesToRange() is a Range method that affects a Sheet Object that is in the same Spreadsheet Object as the Range. There isn't an atomic method that will copy a range of values to another Spreadsheet, but there are a number of ways you could do it.
Here's one way.
From the source sheet, get all the data in one operation, by selecting the complete range of data using getDataRange() and then grabbing all values into a javascript array with getValues().
To ignore the first row of headers, use the javascript array method splice().
Locate your destination, which is on the target sheet, starting after the last row of data that's currently there, using getLastRow().
Write the source data (without the headers) to the destination sheet starting at the next row, using setValues().
Script:
function transferList() {
var sourceSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("RFP List");
var sourceData = sourceSheet.getDataRange().getValues();
sourceData.splice(0,1); // Remove header
var targetSS = SpreadsheetApp.openById("0ABCD").getSheetByName("RFPData");
var targetRangeTop = targetSS.getLastRow(); // Get # rows currently in target
targetSS.getRange(targetRangeTop+1,1,sourceData.length,sourceData[0].length).setValues(sourceData);
}
For some historic dashboard I created by importing and appending data, I use an add-on called Sheetgo. Save me programing time and help me with traceability issues.